SteGriff

Blog

Next & Previous

Migrate MSUnit tests Accessors to PrivateObjects

VSTS Passing Tests

MSUnits tests are fussy at the best of times and don't run in a number of popular environments, such as Travis. If you have invested a lot into white-box MSUnit tests already and need to give your tests better compatability (with Visual Studio Online/Team Services for example) then you can move away from using Accessor types to using PrivateObject. For more pros/cons, scroll to the bottom of the article.

These examples are in VB and C#:

How to

Find places where you create Accessor objects:

Dim target As New MyThing_Accessor() 'Old VB
var target = new MyThing_Accessor(); //Old C#

Change them to create a normal object instance and add a PrivateObject instantiator below:

'New VB
Dim target As New MyThing()
Dim pTarget As New PrivateObject(target)

//New C#
var target = new MyThing();
var pTarget = new PrivateObject(target);

Now find the places you call the methods through your _Accessor class:

Dim actual As String = target.FullActiveFileNameWithoutExtension(mockWorkbook) 'Old VB
var actual = target.FullActiveFileNameWithoutExtension(mockWorkbook); //Old C#

Change them to call pTarget.Invoke with the string name of your method, passing all parameters afterwards (in ParamArray style):

'New VB
Dim actual As String = CStr(pTarget.Invoke("FullActiveFileNameWithoutExtension", mockWorkbook))

//New C#
var actual = (string)pTarget.Invoke("FullActiveFileNameWithoutExtension", mockWorkbook)

Note that because Invoke is dynamic, we no longer have a guarantee (at design time) of what Type will be returned, so you need to convert or cast it to the expected type. We do this above with CStr() or (string).

Anywhere you need to get/set a private field or property, you can use pTarget.SetFieldOrProperty like this:

'VB
Dim target As New MyThing()
Dim pTarget As New PrivateObject(target)
pTarget.SetFieldOrProperty("LastMasterFileName", "C:\Users\Bob\Documents\MyWorkbook.xlsm")
...

Simple invocation example

Here's a simple test around a method for getting file paths for Excel workbooks (it has to work for local and remote paths, and is basically a wrapper around some Path methods - I'm not reinventing the wheel, honest). Extra comments added to show changes.

This is in VB (C# conversion is exercise for the reader):

Before

<TestMethod(), _
 DeploymentItem("MyAddIn.dll")> _
Public Sub FullActiveFileNameWithoutExtension_WindowsFileName()
    'Set up the accessor to let us test the private method
    Dim target As MyThing_Accessor = New MyThing_Accessor()

    'Create mock workbook to pass into the method
    Dim mockWorkbook As New MockWorkbook("C:\Users\Bob\Documents\MyWorkbook.xlsm")

    'Test
    Dim expected As String = "C:\Users\Bob\Documents\MyWorkbook"
    Dim actual As String = target.FullActiveFileNameWithoutExtension(mockWorkbook)
    Assert.AreEqual(expected, actual)
End Sub

After

<TestMethod(), _
 DeploymentItem("MyAddIn.dll")> _
Public Sub FullActiveFileNameWithoutExtension_WindowsFileName()
    'Create the object using its regular constructor
    Dim target As MyThing = New MyThing()

    'Build the private object reflector
    Dim pTarget As New PrivateObject(target)

    'Create mock workbook to pass into the method
    Dim mockWorkbook As New MockWorkbook("C:\Users\Bob\Documents\MyWorkbook.xlsm")

    'Call the private method using Invoke on our pTarget
    Dim expected As String = "C:\Users\Bob\Documents\MyWorkbook"
    Dim actual As String = CStr(pTarget.Invoke("FullActiveFileNameWithoutExtension", mockWorkbook))
    Assert.AreEqual(expected, actual)
End Sub

Longer example

Here's a more complex example with field changes and more parameters in invocation:

Before

<TestMethod(), _
 DeploymentItem("MyAddIn.dll")> _
Public Sub WorkingCopyFileName_FromWindowsFileName_ByKnowingLastMasterFileName()

    Dim target As MyThing_Accessor = New MyThing_Accessor()
    target.LastMasterFileName = "C:\Users\Bob\Documents\MyWorkbook.xlsm"

    'Active filename is usually obtained from FullActiveFileNameWithoutExtension
    Dim activeFileName As String = "C:\Users\Bob\Documents\MyWorkbook_abcd1234"

    Dim expected As String = "C:\Users\Bob\Documents\MyWorkbook.xlsm"
    Dim actual As String = target.WorkingCopyFileURI(activeFileName, Nothing)
    Assert.AreEqual(expected, actual)
End Sub

After

<TestMethod(), _
 DeploymentItem("MyAddIn.dll")> _
Public Sub WorkingCopyFileName_FromWindowsFileName_ByKnowingLastMasterFileName()

    Dim target As MyThing = New MyThing()
    Dim pTarget As New PrivateObject(target)

    'Instead of setting field directly, use SetFieldOrProperty on PrivateObject
    pTarget.SetFieldOrProperty("LastMasterFileName", "C:\Users\Bob\Documents\MyWorkbook.xlsm")

    'Active filename is usually obtained from FullActiveFileNameWithoutExtension
    Dim activeFileName As String = "C:\Users\Bob\Documents\MyWorkbook_abcd1234"

    'Pass all parameters to WorkingCopyFileURI after the method name
    Dim expected As String = "C:\Users\Bob\Documents\MyWorkbook.xlsm"
    Dim actual As String = CStr(pTarget.Invoke("WorkingCopyFileURI", activeFileName, Nothing))
    Assert.AreEqual(expected, actual)
End Sub

Conclusion

Pros:

Cons:

Good luck, have fun...