Skip to main content

Unit testing and PInvoking pt.3

In the previous parts of this short series (that took a long time to finish) I looked at testing code that depends on pinvoke calls. I did this by wrapping the PInvoked functions in a class and then injecting that class into the depending code. I used the wrapper class to put some checks around the platform invokes to smooth some rough edges of the calls to unmanaged land. This creates a nice reusable interface around these calls but now we have the same problem we started with, testability. In this post I'll look at adding a final layer of indirection to be able to test this code.

You can find the previous posts in this series here.

First I'll start with a warning. Because C# forces us to import Dll functions as static functions it's hard to mock them without writing extra code that doesn't really add any functionality. But in most cases I think the added testability is worth the extra verbosity. But it's not the nicest code ever written.

Now lets look back at where we were.

   1: public class SetupApi
   2: {
   3:     [DllImport( "setupapi.dll", 
   4:         EntryPoint = "SetupDiClassNameFromGuid", 
   5:         CharSet = CharSet.Auto, 
   6:         SetLastError = true)]
   7:     private static extern bool SetupDiClassNameFromGuidEP(
   8:         ref Guid classGuid, StringBuilder className, UInt32 classNameSize, out UInt32 requiredSize);
   9:  
  10:     public virtual bool SetupDiClassNameFromGuid(Guid classGuid, out string className)
  11:     {
  12:         // 50 is a Sensible default value, if we're lucky it's enough
  13:         uint reqSize = 50;
  14:         bool returnValue;
  15:         StringBuilder classNameBuilder = new StringBuilder((int)reqSize);
  16:  
  17:         // First try.
  18:         returnValue = SetupDiClassNameFromGuidEP(
  19:             ref classGuid, classNameBuilder, (uint)classNameBuilder.Capacity, out reqSize);
  20:  
  21:         if ((uint)classNameBuilder.Capacity != reqSize)
  22:         {
  23:             // call again with the right size stringbuilder
  24:             classNameBuilder.Capacity = (int)reqSize;
  25:             returnValue = SetupDiClassNameFromGuidEP(
  26:                 ref classGuid, classNameBuilder, reqSize, out reqSize);
  27:         }
  28:  
  29:         className = classNameBuilder.ToString();
  30:         return returnValue;
  31:     }
  32: }

The SetupApi is mockable with rhino mocks so that we can test consuming code. Now let's see how we can test the SetupDiClassNameFromGuid function.


If you're using TypeMock you can stop reading now. TypeMock is a very powerfull mocking framework that can easilly mock the static DllImport. I'm using RhinoMocks. This means I'll have to do some extra work.


First the ugly stuff I warned you about at the start of the article. We'll have to put an adapter around the DllImported function to make it non-static.



   1: private bool SetupDiClassNameFromGuid(
   2:     ref Guid classGuid, StringBuilder className, UInt32 classNameSize, out UInt32 requiredSize)
   3: {
   4:     return _SetupDiClassNameFromGuid(ref classGuid, className, classNameSize, out requiredSize);
   5: }

Now we could just make this function public and use a partial mock to insert our test-code but this would open up the raw DllImport to the outside world. This is not something I'm a big fan of. And we have three different functions called something like SetupDiClassNameFromGuid. So things are getting a bit confusing.


This is the way I usually clean up this mess. With an inner class for the DllImport and some constructor magic to make things injectable.



   1: public class SetupApi
   2: {
   3:     public class DllImport
   4:     {
   5:         [DllImport("setupapi.dll",
   6:             EntryPoint = "SetupDiClassNameFromGuid",
   7:             CharSet = CharSet.Auto,
   8:             SetLastError = true)]
   9:         private static extern bool _SetupDiClassNameFromGuid(
  10:             ref Guid classGuid, StringBuilder className, UInt32 classNameSize, out UInt32 requiredSize);
  11:  
  12:         public virtual bool SetupDiClassNameFromGuid(
  13:             ref Guid classGuid, StringBuilder className, UInt32 classNameSize, out UInt32 requiredSize)
  14:         {
  15:             return _SetupDiClassNameFromGuid(ref classGuid, className, classNameSize, out requiredSize);
  16:         }
  17:     }
  18:  
  19:     private DllImport _import;
  20:  
  21:     public SetupApi() : this(new DllImport()) { }
  22:  
  23:     public SetupApi(DllImport import)
  24:     {
  25:         _import = import;
  26:     }
  27:  
  28:     public virtual bool SetupDiClassNameFromGuid(Guid classGuid, out string className)
  29:     {
  30:         // bla...
  31:  
  32:         returnValue = _import.SetupDiClassNameFromGuid(
  33:             ref classGuid, classNameBuilder, (uint)classNameBuilder.Capacity, out reqSize);
  34:  
  35:         // bla...
  36:     }
  37: }

As you can see I added a parameterless constuctor for use in normal code. Normally I don't do this but in this case we're pretty sure the only two uses of this class will be with the inner class in day-to-day life and with a mocked inner class for tests. So this will clean up calling code a bit.


Here is what a test might look like, here we're testing if our wrapper calls the pinvoked call with an initial buffer of 50.



   1: [Test]
   2: public void ShouldTryCallWithBufferLength50()
   3: {
   4:     var mockDll = MockRepository.GenerateMock<SetupApi.DllImport>();
   5:     var api = new SetupApi(mockDll);
   6:  
   7:     string className;
   8:     var classGuid = Guid.NewGuid();
   9:  
  10:     mockDll.Expect(
  11:         x => x.SetupDiClassNameFromGuid(
  12:             ref Arg<Guid>.Ref(Is.Anything(), classGuid).Dummy,
  13:             Arg<StringBuilder>.Is.Anything, 
  14:             Arg<uint>.Is.Equal((uint)50), 
  15:             out Arg<uint>.Out(51).Dummy))
  16:         .Return(true);
  17:  
  18:     api.SetupDiClassNameFromGuid(classGuid, out className);
  19:  
  20:     mockDll.VerifyAllExpectations();
  21: }

And another one to see if there's a retry when the buffer is bigger than 50



   1: [Test]
   2: public void ShouldRetryIfStringTooLarge()
   3: {
   4:     var mockDll = MockRepository.GenerateMock<SetupApi.DllImport>();
   5:     var api = new SetupApi(mockDll);
   6:  
   7:     string className;
   8:     var classGuid = Guid.NewGuid();
   9:  
  10:     mockDll.Stub(
  11:         x => x.SetupDiClassNameFromGuid(
  12:              ref Arg<Guid>.Ref(Is.Anything(), classGuid).Dummy,
  13:              Arg<StringBuilder>.Is.Anything,
  14:              Arg<uint>.Is.Equal((uint)50),
  15:              out Arg<uint>.Out(51).Dummy))
  16:         .Return(true);
  17:  
  18:     api.SetupDiClassNameFromGuid(classGuid, out className);
  19:  
  20:     mockDll.AssertWasCalled(
  21:         x => x.SetupDiClassNameFromGuid(
  22:             ref Arg<Guid>.Ref(Is.Anything(), classGuid).Dummy,
  23:             Arg<StringBuilder>.Is.Anything,
  24:             Arg<uint>.Is.Equal((uint)50),
  25:             out Arg<uint>.Out(51).Dummy));
  26: }

By the way. I really like the rhino mocks AAA model. Using it together with the Arg object like here makes it still rather readable to mock and assert calls to rather complex functions with ref and out parameters mixed together. Although I did find one small problem. If you remove the cast to uint on line 24 the code still compiles but the test fails. Int 50 should be equal to uint 50 but this doesn't work here. But this could be a problem with the rhino mocks 3.5 beta I'm using.

Comments

Popular posts from this blog

Using xUnit.Net with .Net 4.0

I’ve been using xUnit.Net for a while now. It’s just a tiny bit cleaner and slightly less abrasive than other .Net unit testing frameworks. Leaving out unnecessary stuff like [TestFixture] and shortening Assert.AreEqual to the equally clear but shorter Assert.Equal don’t seem like big improvements but when you type them several times a day tiny improvements start to add up. I also like the use of the [Fact] attribute instead of [Test]. It shifts the focus from testing to defining behavior. So how do we get all this goodness working with the Visual Studio 2010 beta?

Running a Git repository on Ubuntu using Gitosis

20I’ve been using Git for a couple of small projects that I’ve been hosting on github.com but version control for my bigger ‘secret’ projects still runs on a windows machine with visual svn server.Now that I’m starting to use Mono for a couple of projects so I’m playing with linux more. Last week I decided to try to try out gitosis on an ubuntu server. I found out it’s pretty easy to use when you know your way around git but for a noob like me some things weren’t immediately clear. Eventually I solved most problems I ran into, so I decided to write up the steps I took to install gitosis on ubuntu 10.04

Android development resource links

I've been playing with the Android SDK and I have a growing list of bookmarks to Android dev resources for my own use. I thought the best place to keep them would be here on my blog. That way other people can benefit too. I'll keep updating this list so feel free to add suggestions in the comments.