views:

85

answers:

5

Can anyone guide me with a suggested means of testing a private field in a class that is modifed via a public method. I've read a lot of comments from people suggesting that testing private members is not advised as they are internal to the implementation, however this scenario seems to be different from most other answers.

I was thinking about making the private field protected and creating a test subclass that exposes the field but what if I couldn't modify the internals of the class?

In the sample code below the private member to test would be _values which is a write only collection receiving new values via AddValue().

public class Sample
{

    private Dictionary<string, string> _values;
    private DateTime _created;

    public Sample()
    {
        _values = new Dictionary<string, string>();
        _created = DateTime.Now;
    }

    public void AddValue(string key, string value)
    {
        _values.Add(key, value);
    }
}
+4  A: 

You still don't need to test your private variables. You test your interfaces. In this case, I would probably add a Count property and use that for some tests (or something along those lines)

BioBuckyBall
Maybe I'm thinking about it to much. Adding a simple Count property would indeed work well.
WDuffy
+2  A: 

One option you'd have, if you wanted to verify that _values contains what you expect after you've made a call to Sample.AddValue would be to change:

private Dictionary<string, string> _values;

To be

internal Dictionary<string, string> _values;

And then add an InternalsVisibleTo attribute to the AssemblyInfo.cs for your project, referencing your test project. This would then expose the _values field to your test project.

It's far from ideal, because you'd be "testing" an internal implementation detail, but if it's that or nothing, it's a starting point!

Rob
Interesting idea Rob, yucky, but like you said it's a starting point
WDuffy
This is how I've always seen it done.
Mitch R.
+2  A: 

I think the idea in unit tests is to test the behaviour of an object by its public surface, not by its internal implementation. In other words, you shouldn’t be accessing _values in your test; you should instead call the public AddValue() method to add a few keys/values, and then some other public method, e.g. GetKeys() or something, to get the information out again. Then your unit test should test whether that information is correct given the information that went in.

The main reason for this is that the unit test shouldn’t make any assumptions about the implementation details. Imagine you need to change the Dictionary<> into a SortedDictionary<> later, for whatever reason, or you suddenly need two separate dictionaries. You shouldn’t need to make any changes to the unit test for this to work; you should be able to change the internal implementation and then verify its correctness using the same unit test that you’ve already got.

Timwi
Not sure why you were downvoted Timiwi, but the internal dictionary can't be public, it needs to be private and write only.
WDuffy
@WDuffy: Nothing in my answer suggests that it would be public. It is unclear what you mean by “write-only”. If you genuinely only ever write to the dictionary and never read from it again, then it serves no purpose and you can remove it entirely.
Timwi
A: 

As Rob pointed out it's not recommended you have access to private fields, but if you desperatly have to and can't actually test the value being asigned to that field you can do it this way:

        Type sampleType = sampleInstance.GetType();
        FieldInfo fieldInfo = sampleType.GetField("_values", BindingFlags.Instance, BindingFlags.NonPublic);
        Dictionary<string, string> info = fieldInfo.GetValue(sampleType);
linkerro
+2  A: 

I think this would be an example where dependency injection might help.

What's happening here is that you want to test whether an internal object (the dictionary _values) was updated correctly by a call to AddValue. You could achieve this by injecting a mock dictionary into your class-under-test.

This could be done e.g. as follows. First, you'd have to change your Sample class a little bit:

public class Sample
{
    private IDictionary<string, string> _values = new Dictionary<string, string>();

    protected virtual IDictionary<string, string> GetDictionary()
    {
        return this._values;
    }

    public void AddValue(string key, string value)
    {
        GetDictionary().Add(key, value);
        //    ^^^
        // notice this!
    }
}

This now allows you to replace the default dictionary with another one (which you can observe in your test setup) by deriving from your Sample class and injecting a mock dictionary by overriding the InitializeDictionary method:

// this derived class is only needed in your test project:
internal class SampleTest : Sample
{
    public SampleTest(IDictionary<string, string> dictionaryToUse)
    {
        this._dictionaryToUse = dictionaryToUse;
    }

    private IDictionary<string, string> _dictionaryToUse;

    protected override IDictionary<string, string> GetDictionary()
    {
        return this._dictionaryToUse;
    }
}

In your test setup, you can now test this SampleTest class instead of your Sample class. This should be OK since the derived class is identical except that it allows you to specify the dictionary it will use internally. A unit test for checking AddValue could now look like this:

[Test]
public void AddValue_addSomething_DictionaryHasOneAdditionalEntry()
{
    var mockDictionary = new Dictionary<string, string>();
    var sample = new SampleTest(mockDictionary);
    var oldCount = mockDictionary.Count;

    sample.AddValue(...);

    Assert.AreEqual(oldCount + 1, mockDictionary.Count);
}

Disclaimer: I'm by no means a unit testing expert, so my example might be flawed or even be way too complicated. My intention was merely to demonstrate that you can test internal properties of a class if you design your class in a reasonably testable way -- e.g. by allowing the means of dependency injection.

stakx
Yes stakx, I like it.
WDuffy
That's the way I would do it.
xpmatteo