Home > Software engineering >  Writing unit tests for classes that depend on external libraries/sdks
Writing unit tests for classes that depend on external libraries/sdks

Time:11-10

My code consists of the following components:

1 - IMyService: interface that defines properties/methods of the class that will implement it.

2 - MyService: concrete implementation of IMyService.

public interface IMyService 
{
    Task<MyResult> CreateAsync(string id, string json);
}

internal class MyService : IMyService
{
    private readonly ExternalService _externalService;

    public class MyService(ExternalService externalService) 
    {
        this._externalService = externalService;
    }

    public async Task<MyResult> CreateAsync(string id, string json)
    {
        ServiceResult serviceResult = await this._externalService.RunAsync(id, json);

        return new MyResult(serviceResult);
    }
}

MyService class depends on third party library/SDK/package called ExternalService and implements method CreateAsync.

CreateAsync method takes 2 input parameters, invokes RunAsync method on ExternalService, receives result and creates my own class from result.

Question 1)

How would I approach unit testing CreateAsync method in MyService class? Is there any value in writing unit tests for it at all since it contains no logic?

Question 2)

ExternalService has its own Exceptions that it can throw when executing RunAsync method - should I capture exceptions thrown by RunAsync (wrapping try/catch around it) and cast them to my own Exception? For example - ExternalServiceException => MyServiceException.

CodePudding user response:

Mock it with Moq and Autofixture. I don't know what unit testing framework you intend to use. So I would use Xunit as an example. If it is calls to an external service you're only interested in testing what it can return.

First create an autofixture class. This is an attribute that is necessary for our test cases. More info here

public class AutoMoqDataAttribute : AutoDataAttribute
{
    public AutoMoqDataAttribute()
        : base(new Fixture()
            .Customize(new AutoMoqCustomization()))
    {
    }
}
[Theory]
[AutoMoqData]
public async Task Validate_Service_ReturnResult([Frozen]Mock<IMyService> someService, ServiceResult mockResult, SomeClass sut){
    //Set up interface to return what you want. 
    //In this case it returns mockResult always, 
    //but you can edit the properties of this to be whatever best fits your test case.
    someService.Setup(x => x.CreateAsync(It.IsAny<string>(), It.IsAny<string>()))
               .ReturnsAsync(mockResult);
    var result = await someService.Object.CreateAsync("ree", "skee");
    
    //The class we are actually testing
    var res =sut.DoSomething("someId", "someJson")

    Assert.NotNull(res);
}

The [Frozen]-attribute makes sure that the behavior of the mocked someService is identical throughout all your code.

Doing all this is argueably a lot better than "faking" your interface for each testcase. Just imagine that you want CreateAsync to return different httpcodes like 404, 200, or 500. Have fun creating a new implementation of the interface for each case. With autofixture you just have set it up in the the beginning of your testcase.

The class we want to run our tests on

public class SomeClass{
   IMyService someService;
   public SomeClass(IMyservice someService){
       this.someService = someService
   }

   public async Task<string> DoSomething(string id, string json){
       var res = await this.someService.CreateAsync(id, json);
       if(res.HttpStatusCode == 200){
          //then do bla bla bla
       ...
   }
}

Question 1: Yes. It is definitely worth it to unit test your services. You need to see if your code handles negative results. It would suck if this was a service that crashed because a result wasn't handled properly. For example if you expected an object from the service but receives null. Then before long you will have a null reference exception and a crashed service.

Question 2: Doesn't matter. Either way exceptions should be logged. If there is something important you want logged so you can query it easily eg. HttpCode. Then please do. It depends on two things. First: Can you handle the exceptions? And second: Do you log your exceptions to some central database?

  • Related