4

I'm trying to write unit tests for business logic classes I have control on, but which operates over some services that are not designed with the testability in mind. Currently I’ve extracted the problematic services as constructor injected ones, but I still need to mock them during the arrange part of the tests. The approach I intend to take is to wrap every non controlled service and to delegate the actual work to the wrapped service. I'm considering to:

  • Option A. to implement a Facade and to expose only the subset of the service API that's in use, delegating the actual implementation to the aggregated service.
  • Option B. to implement a Decorator, just for the sole purpose to provide an interface later to mock on.

Is there a better approach? Which of the two makes more sense? Pros and cons?

Thank you in advance!

Option A

public class OriginalService
{
    public int Property1 { get; set; }
    public bool DoSomething1(int param1) { ... }
}

interface IWrapperService
{
    int Property1 { get; set; }
    bool DoSomething1(int param1);
}

public class WrapperService : IWrapperService
{
    private readonly OriginalService _service;
    
    public WrapperService(OriginalService service)
    {
        _service = service;
    }
    
    public int Property1 
    { 
        get { return _service.Property1; }
        set { _service.Property1 = value; }
    }
    
    public bool DoSomething1(int param1) 
    {
        return _service.DoSomething1(param1) ;
    }
}

Option B

public class OriginalService
{
    public int Property1 { get; set; }
    public bool DoSomething1(int param1) { ... }
}

interface IWrapperService
{
    int Property1 { get; set; }
    bool DoSomething1(int param1);
}

public class WrapperService : OriginalService, IWrapperService { ... }
6
  • You could use Reflection and generate a stub mock class. Aug 4 '20 at 10:34
  • We don't generally give advice on frameworks here. There are a number of decent ones for the .net platform. A good way to discover them is to use nuget and search for the word mock. This will find you a number of such libraries, and give you an indication of how well used they are.
    – Kain0_0
    Aug 4 '20 at 12:05
  • 1
    @Kain0_0: There's a difference between OP asking for a framework suggestion and OP asking a question which could be solved easier using a certain framework. The latter is on topic.
    – Flater
    Aug 4 '20 at 12:06
  • 1
    Just to be clear about my earlier comment: OP is indeed asking for a framework, but also for a DIY solution. The question is valid, but it should maybe omit the specific request for a framework - or at least not put it first and foremost as it's going to (IMHO wrongly) draw close votes for appearing to ask an off-topic question.
    – Flater
    Aug 4 '20 at 12:16
  • I'e made my question more specificity toward which approach i should take.
    – kalitsov
    Aug 4 '20 at 12:18
3

For your specific question, options A and B are effectively equal, i.e. you superimpose an interface on a class which has none, by wrapping it in a class that does have the interface.

It's less ideal than just putting the interface on the base class, but since you don't control the original class' design, that's not an option.

It's usually advised to favor composition over inheritance, but if you never intend to actually wrap any logic around the original service (it's a clean passthrough), then inheritance is perfectly fine. I presume you'll be mocking using the interface, not the concrete wrapper class, so I don't see any issue here.


Additionally, libraries like Moq or NSubstitute can give you mocking functionalities without having to develop them yourself.

Using an NSubstitute example because it's the one I'm most familiar with:

var sub = Substitute.For<IWrapperService()>;

It's not hard to make a mocked class which implements IWrapperService yourself, but the library also gives you a lot of methods on this object that allow you to easily mock responses. That's just better than having to write the whole mocking setup logic yourself. For example:

sub.DoSomething1(123)  // that number is only there to satisfy the compiler
   .ReturnsForAnyArgs(false);

You can even specify specific return values for specific input values:

sub.DoSomething1(Arg.Is<int>(p => p > 25))
   .Returns(true);

sub.DoSomething1(Arg.Is<int>(p => p <= 25))
   .Returns(false);

This is just a basic example. When you start using it, you'd be surprised how much power this gives you to quickly bang out a test fixture when writing tests.

As far as my experience with Moq does, it provides the same features, just using a slightly different syntax.

2
  • Thanks for the opinion! The second part of your answer is not correct though, as i'm trying to mock a class most of which methods are not virtual. And this is the main purpose of doing the extra work.
    – kalitsov
    Aug 4 '20 at 13:00
  • @kalitsov: Point taken, I did miss the lack of virtual. The suggestion is still valid for when you are mocking your IWrapperService though. I'll adjust the answer accordingly.
    – Flater
    Aug 4 '20 at 13:09
0

The problem with B is that your interface method has to exactly match the wrapped method signature.

If you are refactoring out a lot of calls and want to do it with minimal changes and typing then this might be fine.

But generally I have found that there is often extra logic around calling the 3rd party object which you would idealy want to include behind the interface.

For example, say its a database client, and you want to retrieve some data. Your code might have several calls to the client,

  • open the connection
  • start a transaction
  • get the data
  • parse the data
  • close transaction
  • close connection

If we use option B all of these methods and their return types will need to be in the interface. Setting up the mocks for the test will be complex and potentially error prone. You'll have a lot of "test code"

If we use option A we can move all these calls into our wrapper class and have a single method in the interface. Your tests will be simple by comparisom and have less "test code"

This requires more refactoring of the code above the minimal required for testing, which might be problematic if you have no exisitng tests. But the end result will be better code and better tests.

0

There is no need to add additional abstraction to your logic that is not required outside of unit testing. If you only need to unit test, it use a modern-day mocking framework that can mock a concrete class. An example of such a tool is JustMock.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.