Usually, I try to implement my unit tests by testing only publicly available members of a class.
These kinds of tests are called black-box tests since they don't care what's happening inside the entity being tested,
but only what outputs it generates for any given input.
I find, however, that often you'll also want to test protected and private members of a class.
Some of the possible reasons for doing that:
- a private method performs calculations that are not that easy to test via accessing public methods only
(all code paths are hard to execute, the algorithm is pretty complex,
the test is difficult to setup without accessing that method, etc.),
- a private field needs to be set to a specific value for the test to run,
but you don't want to make that field neither protected nor public,
- methods are hidden not because their implementation may change, but because you want to show developers
how they should access an object (so, the argument of doing black-box testing because the internals may change
doesn't really apply in this scenario),
- and probably a hundred other reasons for very specific scenarios.
Though you may argue that some of the above are caused by poor design decisions, in real life it's just not that simple
(for instance, you may have inherited a substantial code base and it's not feasible to restructure the code)...
Anyway, if you find that you need to access private fields and/or methods,
PrivateObject
comes to the rescue.
Let's say that we have the following class:
Public Class Comp
Private _field As Integer
Private Function GetMultiple(ByVal multiplier As Integer) As Integer
Return _field * multiplier
End Function
. . .
End Class
Now, because of... whatever..., you need to access these private members in a unit test:
<TestMethod()>
Public Sub TestComp
Dim comp As New Comp()
Dim compAccessor As New PrivateObject(comp)
. . .
End Sub
compAccessor is instantiated with the class instance under test; now, using compAccessor, you can access comp's private members:
<TestMethod()>
Public Sub TestGetMultiple
Dim comp As New Comp()
Dim compAccessor As New PrivateObject(comp)
compAccessor.SetField("_field", 2)
Dim result As Integer = CInt(compAccessor.Invoke("GetMultiple", 5))
Assert.AreEqual(10, result,
"GetMultiple shall return the product of the given value " +
"and the value of the internal _field variable.")
End Sub
So, you can use SetField to set a private variable's field to a value (and GetField to retrieve that value).
And than, you can use Invoke to call a sub or a function; and to retrieve the results of a function.
Note that you need to cast the result of Invoke to the target type.
This is because Invoke is declared as returning an Object
(since it can be called with various functions that return values of types other than Integer).
If you need to pass in multiple values to a method, you just pass an array of values
(let's say that now GetMultiple takes two integers):
<TestMethod()>
Public Sub TestGetMultiple
Dim comp As New Comp()
Dim compAccessor As New PrivateObject(comp)
Dim result As Integer = CInt(compAccessor.Invoke("GetMultiple", {5, 2}))
Assert.AreEqual(10, result,
"GetMultiple shall return the product of the given values.")
End Sub
If you have a private or protected property, instead of using GetField/SetField, you should use GetProperty/SetProperty.
However, it's usually best to just use GetFieldOrProperty/SetFieldOrProperty to handle both scenarios
And, just like with Invoke, you'll need to cast the results of GetField/GetProperty/GetFieldOrProperty to the target type.
Finally, Invoke can also be used with Subs (with zero or more parameters) - it just doesn't return a value then.
Now, this gives you access to instance members; but if you want to access private shared / static members,
instead of using PrivateObject, you'll need
PrivateType.
Works just like private object, but instead of initializing it with an instance,
you pass the class type to the constructor;
and, instead of using Invoke and Get/SetX, you need to use InvokeStatic and Get/SetStaticX
(e.g. GetStaticField, GetStaticProperty, GetStaticFieldOrProperty, and SetStaticField, SetStaticProperty, SetStaticFieldOrProperty):
<TestMethod()>
Public Sub TestGetMultiple
Dim compAccessor As New PrivateType(GetType(Comp))
compAccessor.SetStaticField("_field", 2)
Dim result As Integer = CInt(compAccessor.InvokeStatic("GetMultiple", 5))
Assert.AreEqual(10, result,
"GetMultiple shall return the product of the given value " +
"and the value of the internal _field shared variable.")
End Sub
Happy testing!
Top
|