NMock2 and mocking classes

Peter Wain
2008-10-01
2013-04-29
  • Peter Wain
    Peter Wain
    2008-10-01

    Hi,

    I've put together a solution for use at work which allows mocking of classes with NMock2. It was originally aimed at allowing virtual methods in classes to be mocked out so that other methods in the class can be tested in isolation.

    It's currently implemented externally from NMock2.dll, providing it's own Mockery and proxy infrastructure, but leverages NMocks syntax, matchers, actions and general framework.

    If you guys are interested, I'd like to refactor it back into the NMock2 codebase and submit it for inclusion in a future release. There's a bunch of other stuff I'd like to discuss, but I'll just wait for the go-ahead on this first.

    Cheers,

    Peter.

     
    • Urs Enzler
      Urs Enzler
      2008-10-01

      Hi Peter

      We have to discuss this first in our team but my personal opinion follows:

      Lately, we had some discussions about replacing the proxy generation code with something easier to maintain. We took a look at DynamicProxies, LinFoo Proxy Generator and some other libs, but have not yet come up with a decision.
      This task is not high prio for us, because we enforce the usage of interfaces in our projects, eliminating the need for mocking classes almost completely.

      But anyway, if you would contribute your solution, I'd be glad to have this feature on board. Can you describe your solution in a couple of sentences, especially how you create the proxies?

      For us it'd be great to have support in the area of proxy generation because this is definitely not our strength at the moment :-)

      Finally, I'm very curious what else you what to discuss - good ideas are very welcome.

      Happy mocking
      Urs

       
      • Peter Wain
        Peter Wain
        2008-10-01

        As a general rule, I also tend to abstract any significant dependency behind an interface. Lately though, I've come across a number of situations where a complex algorithm or piece of business logic spans a set of related methods in a single class. Due to the number of possible paths through this code, it was not really practical to test thoroughly from the top down, and it didn't make sense to decompose the code into multiple classes and hide them behind interfaces just to support mocking.

        Being able to focus on testing one of these methods at a time by mocking out the other methods it interacts with is
        invaluable in these situations. As long as a method has at least protected accessiblity and is marked virtual, then it can be stubbed or have expectations set on it. In the past we've had to do this manually with hand-written 'test doubles' that subclass the class under test and override the behaviour of certain methods, returning known values, or collecting input arguments etc. This adds a lot of code to your tests.

        Now, in terms of implementation... For proxy generation I'm using Castle DynamicProxy2. I settled on that mostly because I've become familiar the original DynamicProxy when using and extending Castle MicroKernel and NHibernate. The newer version seems pretty solid.

        My basic approach is to build the System.Type for the proxy, subclassing the class to be mocked and additionally implementing IMockObject. The proxy is then instantiated and injected with a custom IInterceptor which holds the same state as the current MockObject implementation. The interceptor is responsible for routing calls that target IMockObject to itself, and calls to the actual class to the Mockery so that Expectation logic etc can be satisfied. There's some caching going on as well.

        This is a fairly unintrusive change, as the bulk of the current framework only really cares about the mock implementing IMockObject, and this has not changed.

        The same basic approach works for interface proxies too.

        Like you, I had a quick look at LinFu, attracted by the relative performance over DP2. Unfortunately, I couldn't get it's particular take on proxies and interceptors to work the way I wanted. Specifically, I could proxy the intitial call to a method, but if I let the invocation proceed, any other local methods that were called from the target method were not able to be intercepted. This seems to be because a LinFu interceptor is designed to hold a reference to the target of the proxy, rather than the target being the superclass of the proxy itself. However, I'll be happy to be proved wrong...

        I've got a few questions for you, but will put them is a subsequent post.

        Peter.

         
        • Urs Enzler
          Urs Enzler
          2008-10-02

          Hi Peter

          For us it'd be great to integrate your proxy generator into NMock2.
          If possible, we'd like you to replace the current proxy generator with your one and send us a svn patch. This way we can test it and if everything's okay we integrate it. The tests include some special scenarios which cause us problems with the current proxy generator.

          Furthermore, if you have other "things" you'd like to contribute, why not join our team?

          Thanks a lot for your effort.
          Urs

           
          • Peter Wain
            Peter Wain
            2008-10-02

            Hi Urs,

            Thanks - I'd like to join the team. Let me know if there is anything you need from me to make that happen.

            I'm slowly but surely refactoring what I've got into something that makes sense for the current NMock2 codebase. Hopefully I'll have a patch for you soon.

            Let me know which tests are giving you trouble (I'm assuming that they're in the source tree somewhere).

            I do have a question for you (or anyone else)...

            What's the deal with naming mock instances via NewMock<T>(string name) and related methods? I can't say that I've ever needed to do this in the several years I've been using NMock2. I understand why it might be desirable to have an automatically generated, meaningful name for use with the ToString() method on interface proxies, to make exception messages etc read better. I'm just not so sure that users will ever want to control this from the public API.

            I don't have a problem with the current API, or the ability to name interface mocks; it's just that this concept does not translate well over to class mocks. These are the reasons:

            1) Overriding ToString() on a mocked class is probably not safe to do, as it may have a special meaning. However, I've got a workaround for this which still allows the mocks name to be surfaced in exception messages etc.

            2) It complicates the API for creating new mocks. I've wrestled with this a lot, and have currently settled on the following additional methods on Mockery (plus their non-generic counterparts):

               T NewClassMock<T>(params object[] args)
               T NewClassMock<T>(MockStyle mockStyle, params object[] args)

            I tried to have NewMock<T>() service both interface and class mocks, but it quickly got messy. The arguments in the above signatures are only really relevant for class mocks, and are both conceptually optional.

            I won't go into much detail here, but adding additional overloads for a 'name' parameter starts to make the method signatures ambiguous. IE a call that was intended for one overload actually ends up targetting a different overload. This can be worked around by only allowing the name parameter to be specified on the most verbose method overload, and in an order that cannot be misinterpreted. EG:

               T NewClassMock<T>(string name, MockStyle mockStyle, params object[] args)

            Kind of ugly though.

            So, I'd be interested to hear your thoughts. Should we not allow names to be specified for class mocks, or should we go the extra mile and try and make it work?

             
    • Urs Enzler
      Urs Enzler
      2008-10-02

      Hi Peter

      Just a short answer for now - I'm in a hurry ;-)

      We use the naming feature only for one single purpose (maybe there are other reason, I don't know), if we have to mock the return value of the ToString() method.
      For example if you have a test the checks whether your newly written class implements a meaningful ToString() implementation. Because ToString() is not part of the interface (I just added a check for that in the last revision!) the only way to set an expectation on this method is to use a named mock. The named mock will return its name when ToString() is called.

      That means that ToString() has to suit two purposes: 1. giving mocks meaningful names in error messages (especially if there are several mocks of the same type) and 2. mocking of the ToString() method return value.

      To sum it up, if there is another way to mock ToString() when mocking classes and using the workaround you mentioned in your post then we can leave the name out of the mock creation methods.

      In my opinion it'd be even better this way, because then the method ToString() would not have to fulfill two different things.

      You'll get an email to your sf.net email address with the stuff about the team.

       
    • Peter Wain
      Peter Wain
      2008-10-03

      Ok, I never considered that usage...

      The approach that I am proposing is to introduce a new member on IMockObject called MockName. This will have the mocks name (either manually specified or automatically generated) assigned to it when a mock instance is instantiated. All the call sites that currently use ToString() for generating exception messages can be switched over to using MockName instead (there's not too many).

      For interface mocks, we can still return the mocks name from the ToString() method.

      For class mocks however, I think that it's safest to just to leave ToString() as it is. I'll have to write some tests to validate this, but I believe there is no problem stubbing or placing expectations on a class mocks ToString() method provided that it has been overridden from the default System.Object implementation.

       
      • Peter Wain
        Peter Wain
        2008-10-07

        Ok, have created a new feature request (2151374) and uploaded a patch for supporting class mocking.

        The Castle assemblies are currently additional dependencies that NMock will have to cart around, but I'm pretty sure that they can be merged into the NMock2.dll using ilmerge.exe. I'll look into this shortly...

        I've got some tests that cover the basic functionality of class mocking, but they are by no means exhaustive. I played around with refactoring some of the current test fixtures to better support testing of both interface and class mocks. Take a look at EventsAcceptanceTest.cs and let me know if you think that approach is suitable or not.

        Urs: This patch alters the behaviour of some of your recent changes regarding error messages when attempting to mock a class type rather than an interface type. If you're not happy with the changes I made, please let me know.