Monday, September 18, 2006
Posted on Monday, September 18, 2006 2:24:06 PM (Mountain Daylight Time, UTC-06:00)  Comments [4] | 
Categories: .NET | ArcGIS Devt | Team System
Things are going well with our ArcDAL project (Code Generation + Geodatabase = Goodness), and now that our base class library is pretty solid, I'm adding in unit tests. We want these tests to be run on every build, on any development system, so I want as much separation between the tests and any underlying required resources - for example - ArcSDE instances. Conveniently Scott Hanselman's podcast from last week was all about using Mock objects in unit testing.

What is a Mock?
Basically, a mock object is a "fake" object that is created solely for the purpose of running a test. It's generated on the fly using some mocking framework, and you basically tell it what to expect in terms of requests (properties / methods) and what to return. One place where this is really handy is when you want to avoid binding your tests to a specific instance of ArcSDE, or a specific file location for a personal geodatabase. It can also be used to avoid having to make calls out to other ArcObjects if you don't actually need to.

I'll go through a simple example to show what I mean...

Example:
Before I go any further, let me say that I'm using Visual Studio 2005, and the MSTest framework, but this will be pretty much the same in VS2003, and/or NUnit.

We'll look at the tests for a Workspace Utility class...


This class just has 3 very simple shared methods which return information about an IWorkspace. The thing is that in order to test this with real instances IWorkspace, we'd need to actually connect to ArcSDE and a personal geodatabase. And this will make the tests brittle, as it's unlikely that we'll keep an instance up for a long time, and file paths will be different on different development machines.

Thus, we use Mock objects to "fake" the workspace. I've chosen to use the NMock2 framework for two basic reasons: a) free and b) works.

I've gone ahead and had Visual Studio create a set of tests for this class for me. This is a pretty handy feature of VS2005 - it looks at your classes and spits out a basic test harness to use as a starting point.

    '''<summary>

    '''A test for IsAccessWorkspace(ByVal ESRI.ArcGIS.Geodatabase.IWorkspace)

    '''</summary>

    <TestMethod()> _

    Public Sub IsAccessWorkspaceTest()

        Dim workspace As IWorkspace = Nothing

        Dim expected As Boolean

        Dim actual As Boolean

        actual = ArcDAL.WorkspaceUtil.IsAccessWorkspace(workspace)

        Assert.AreEqual(expected, actual, "Did not return the expected value.")

    End Sub


Now, if we were doing this without mocks, we'd need to actually connect to a personal geodatabase, and pass that into our WorkspaceUtil class. Like this...

    '''<summary>

    '''A test for IsAccessWorkspace(ByVal ESRI.ArcGIS.Geodatabase.IWorkspace)

    '''</summary>

    <TestMethod()> _

    Public Sub IsAccessWorkspaceTest()

        Dim workspacefactory As IWorkspaceFactory = New AccessWorkspaceFactoryClass

        Dim connectionProperties As IPropertySet = New PropertySetClass

        connectionProperties.SetProperty("DATABASE", "C:\Test Data\ArcDAL\SampleData.mdb")

        Dim workspace As IWorkspace = workspacefactory.Open(connectionProperties, 0)

        Dim expected As Boolean

        Dim actual As Boolean

        actual = ArcDAL.WorkspaceUtil.IsAccessWorkspace(workspace)

        Assert.AreEqual(expected, actual, "Did not return the expected value.")

    End Sub


But, now we have a hardcoded path in the test. Now, we could get crafty, and read the file location from a configuration file, but that's not the point of this post!

So - to add in Mock objects, add a reference to NMock2 and include/import the NMock2 namespace in your test class. To make things easier, I setup the mock workspace in the ClassStartup method which runs before the tests are executed.

    Private Shared _mockWorkspace As IWorkspace

    'Startup

    <ClassInitialize()> _

    Public Shared Sub MyClassInitialize(ByVal testContext As TestContext)

        Dim mocks As New NMock2.Mockery

        _mockWorkspace = CType(mocks.NewMock(GetType(IWorkspace)), IWorkspace)

    End Sub


So - this has created a member variable of type "IWorkspace" which is in reality a mock. At this point we just have a mock, but we tell the mock what to "expect" and how to respond in the tests themselves.

    '''<summary>

    '''A test for IsAccessWorkspace(ByVal ESRI.ArcGIS.Geodatabase.IWorkspace)

    '''</summary>

    <TestMethod()> _

    Public Sub IsAccessWorkspaceTest_Mock()

        Expect.Once.On(_mockWorkspace).GetProperty("Type").Will(NMock2.Return.Value(esriWorkspaceType.esriLocalDatabaseWorkspace))             

        Dim workspace As IWorkspace = _mockWorkspace

        Dim expected As Boolean = True

        Dim actual As Boolean

        actual = ArcDAL.WorkspaceUtil.IsAccessWorkspace(workspace)

        Assert.AreEqual(expected, actual, "Did not return the expected value.")

    End Sub


Hopefully the word-wrap cleans up that line, but it's pretty simple, and follows a conversational type of logic.

Expect.Once.On(_mockWorkspace).GetProperty("Type").Will(NMock2.Return.Value(esriWorkspaceType.esriLocalDatabaseWorkspace))



Translates into:

Expect one call on _mockWorkspace to a Property called Type, Return a value of esriLocalDatabaseWorkspace

Pretty simple. The NMock framework takes care of actually "faking" out the IWorkspace, and then returns the specified value when the request it made. Here's a shot of the tests in the results viewer. All green!



So, while this is a very simple example of how to use Mock objects with ArcObjects, I'll be using this same methodology in other places as I add tests through the entire ArcDAL class library. I'm sure that there will be places where Mocks will not work (I suspect IFeature maybe a bit of a bear to mock), but it's a start.