Chris | Posts: 12

How to create test doubles for SDK types

0 votes
We are experiencing difficulties creating unit tests around portions of our code which use the SDK. For example suppose we have a class like:
public class MiscPackageSending
{
    private readonly EslClient eslClient;

    public MiscPackageSending(EslClient eslClient)
    {
        this.eslClient = eslClient;
    }

    public bool SendSomething(PackageId packageId)
    {
        eslClient.PackageService.SendPackage(packageId);

        return true;
    }
}
And then we try to test it:
[Test]
public void StateUnderTest_ExpectedBehavior()
{
    var sut = new MiscPackageSending(new EslClient("test", "test"));

    var result = sut.SendSomething(new PackageId("1"));

    result.Should().BeTrue();
}
Since there is no interface for the EslClient an actual EslClient must be used. As a first attempt to move away from this we have created our own interface and class which inherits the EslClient so we can swap in other implementations.
public interface IEslClient
{
    PackageService PackageService { get; }
}

public class CustomEslClient : EslClient, IEslClient
{
    public CustomEslClient(string apiKey, string baseUrl) : base(apiKey, baseUrl)
    {
    }
}

public class MiscPackageSending
{
    private readonly IEslClient eslClient;

    public MiscPackageSending(IEslClient eslClient)
    {
        this.eslClient = eslClient;
    }

    public bool SendSomething(PackageId packageId)
    {
        eslClient.PackageService.SendPackage(packageId);

        return true;
    }
}
This lets us supply our own implementation, but now when we try to provide a test double for the PackageService:
[Test]
public void StateUnderTest_ExpectedBehavior()
{
    var mockSilanisClient = new Mock();
    mockSilanisClient.Setup(method => method.PackageService.SendPackage(It.IsAny()));
    var sut = new MiscPackageSending(mockSilanisClient.Object);

    var result = sut.SendSomething(new PackageId("1"));

    result.Should().BeTrue();
}
We can't:
System.NotSupportedException : Invalid setup on a non-virtual (overridable in VB) member: method => method.PackageService.SendPackage
So then it looks like we need to do a similar thing with the PackageService.
public interface IEslClient
{
    IEslPackageService PackageService { get; }
}

public class CustomEslClient : EslClient, IEslClient
{
    public CustomEslClient(string apiKey, string baseUrl) : base(apiKey, baseUrl)
    {
        PackageService = new CustomPackageService(base.PackageService);
    }

    public new IEslPackageService PackageService { get; }
}

public interface IEslPackageService
{
    void SendPackage(PackageId pcPackageId);
}

public class CustomPackageService : IEslPackageService
{
    private readonly PackageService eslPackageService;

    public CustomPackageService(PackageService eslPackageService)
    {
        this.eslPackageService = eslPackageService;
    }

    public void SendPackage(PackageId packageId)
    {
        eslPackageService.SendPackage(packageId);
    }
}
And now the test can run. But that's a lot of work to be able to write tests; and this only shows one method, all the methods will need to be implemented and passed through to the actual EslClient now. Additionally this is only one service. And finally, when the SDK changes we will need to be aware and update our own implementations as necessary. So my question is, what other options are there? We could completely abstract away the API or write are own SDK or use the REST client, but all of those will be more work. It seems this pain would be alleviated if the SDK was structured a bit differently, primarily if it used interfaces. Do you have any recommendations?

mwilliams | Posts: 957

Reply to: How to create test doubles for SDK types

0 votes
Hey Chris, I am checking with our SDK developers to see what they would suggest. The SDK is open source, so you could always make changes to it vs rebuilding yourself. I will let you know what I hear back.

Chris | Posts: 12

Reply to: How to create test doubles for SDK types

0 votes
I didn't realize this. Thanks for the suggestion, that is better than recreating it, although then changes will need to be merged when there are updates. Do you know if they will take pull requests?

Hello! Looks like you're enjoying the discussion, but haven't signed up for an account.

When you create an account, we remember exactly what you've read, so you always come right back where you left off