Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit Testing Service Layer in .NET

I am developing a new MVC 5 Application. I have an Integration Layer in my project where I have a service reference to an external web service.

I then created a interface that has some methods - I then have a service class which looks something like below:

public class MyService : IMyService
{
    private readonly ExternalServiceClient _serviceClient;

    public MyService()
    {
        _serviceClient = new ExternalServiceClient ("WSHttpBinding_IMyService");
    }

    public string GetName(Coordinate[] coordinates)
    {
        string name = string.Empty;

        name = _serviceClient.GetInformationsForCoordinates(coordinates);

        return name;
    }

Note this is stripped down and in reality will add try catch block with Exception Handling, etc

I have a Integration.Tests project for Unit Testing of the integration layer. What is the best practice way of testing the GetName method. Should I add another Service Reference to the endpoint of the service in the Tests project or if we are using Moq can I create a instance of my actual service that the Web Layer for example will call - and how would that be done?

like image 799
Ctrl_Alt_Defeat Avatar asked Dec 12 '22 06:12

Ctrl_Alt_Defeat


2 Answers

The best practice for your situation lies in your architecture design.

Your service obviously depends on ExternalServiceClient, and you're intantiating it inside of your MyService class, which doesn't allow you to switch the dependency easily, causing you headaches when it's time to test it.

The real question should be:

How may I design my service in order to make it easily testable?

And the answer is Dependency Injection.

Because you will be able to mock MyService dependency, you will be able to thoroughly test it and be able to certify, by red-green refactoring, that your service works flawlessly.

In my humble opinion, your class should like this:

public class MyService : IMyService {
    public MyService(ExternalServiceClient serviceClient) {
        externalServiceClient = serviceclient;
    }

    public string GetName(Coordinate[] coordinates) {
        string name = string.Empty;
        name = externalServiceClient.GetInformationForCoordinates(coordinates);
        return name;
    }

    private readonly ExternalServiceClient externalServiceclient;
}

This way, you will be able to replace your dependency at your will, hence the use of a mock.

Using NUnit and Moq, you could test your service as follows:

[TestFixture]
public class MyServiceTests {
    [TestFixture]
    public class GetCoordinates : MyServiceTests {
        // Given
        string expected = "Name";
        Coordinate[] coordinates = new Coordinate[] { ... }
        externalServiceClientMock.Setup(esc => esc.GetInformationForCoordinates(coordinates)).Returns(expected);

        // When
        string actual = myService.GetName(coordinates);

        // Then
        externalServiceClientMock.Verify(esc => esc.GetInformationCoordinates(coordinates));
        Assert.AreEqual(expected, actual);
    }

    [SetUp]
    public void MyServiceSetup() {
        externalServiceClientMock = new Mock<ExternalServiceClient>("WSHttpBinding_IMyService");
        myService = new MyService(externalServiceClientMock.Object);
    }
    
    private Mock<ExternalServiceClient> externalServiceClientMock;
    private MyService myService;
}
like image 123
Will Marcouiller Avatar answered Dec 13 '22 19:12

Will Marcouiller


In general, it is good practice (as you have readonly semantic for ExternalServiceClient) to have parametrized constructor to be able to inject the dependency on object construction. Then you can inject the mock in your test case.

In your case, if there is interface(s) that implemented in ExternalServiceClient

private readonly ExternalServiceClient _serviceClient;

public MyService(IExternalServiceClient serviceClient)
{
    _serviceClient = serviceClient;
}

Then use it as:

var service = new MyService(new ExternalServiceClient ("WSHttpBinding_IMyService"));

and in test

IExternalServiceClient  mockObject = //construct mock with desired behabiour then pass to ctor
var service = new MyService(mockObject);

If there is no implemented interfaces and ability to add it (since it is external), you must do some tricks with virtuallity.

like image 34
Hamlet Hakobyan Avatar answered Dec 13 '22 20:12

Hamlet Hakobyan