RobinTTY/NordigenApiClient

Unable to mock response classes

Closed this issue · 12 comments

I'm currently trying to write some unit tests for a project that uses this library. I'd like to be able to mock the NordigenApiResponse<T1,T2> class, and check that my code responds to various response objects appropriately. However I can't do this as the class doesn't have an interface or public constructor/setters.

If this class could have an interface that would be great, although I'm open to other suggestions as to how this could be achieved.

The constructor is now public. Hadn't considered the need to mock this class since I only test with real responses right now. But makes absolute sense to be able to mock it. Didn't want to create an interface only for testing. All changes are released in v6.1.3. Thanks again!

Brilliant thank you :-)

Hi Robin I have one final request (sorry!) In NordigenClient would it be possible to mark the endpoints as virtual? At the moment I am unable to mock them.

Hi Robin I have one final request (sorry!) In NordigenClient would it be possible to mark the endpoints as virtual? At the moment I am unable to mock them.

I was thinking about adding an interface for that class anyway for DI. That would solve your problem as well I guess?

It would indeed!

@whippet71 New interface is in v6.2.0 ;)

Hi Robin sorry to be a pain - I am still having issues trying to mock the endpoints. What I would like to do is something like this:

nordigenClientMock = new Mock<INordigenClient>();
institutionsEndpointMock = new Mock<InstitutionsEndpoint>(nordigenClientMock.Object);
nordigenClientMock.Setup(s => s.InstitutionsEndpoint).Returns(institutionsEndpointMock.Object);
institutionsEndpointMock.Setup(expression: s => s.GetInstitution(
    It.IsAny<string>(),
    It.IsAny<CancellationToken>())).ReturnsAsync(...);

However this is failing, and I think it's because the endpoints require a NordigenClient to be passed in their constructor (not an INordigenClient). And because NordigenClient doesn't have a default constructor, that fails... I am not actually sure what the best solution is here, any thoughts?

Hi Robin sorry to be a pain - I am still having issues trying to mock the endpoints. What I would like to do is something like this:

nordigenClientMock = new Mock<INordigenClient>();
institutionsEndpointMock = new Mock<InstitutionsEndpoint>(nordigenClientMock.Object);
nordigenClientMock.Setup(s => s.InstitutionsEndpoint).Returns(institutionsEndpointMock.Object);
institutionsEndpointMock.Setup(expression: s => s.GetInstitution(
    It.IsAny<string>(),
    It.IsAny<CancellationToken>())).ReturnsAsync(...);

However this is failing, and I think it's because the endpoints require a NordigenClient to be passed in their constructor (not an INordigenClient). And because NordigenClient doesn't have a default constructor, that fails... I am not actually sure what the best solution is here, any thoughts?

No problem at all, I'm happy if I can improve this library a bit. To be honest I'm not an expert when it comes to mocking, so I'll need to test around a bit myself. I'll create some tests with mocks myself and see what needs to change to actually be able to create mocks. Maybe it's enough to pass INordigenClient in the constructor of the endpoints, maybe some more changes are requried... I'll release a new package version once I get around to testing this

That's great, thanks for responding so quickly

@whippet71 fixed in v7.0.0. You can now do something like this:

using System.Net;
using Moq;
using RobinTTY.NordigenApiClient.Contracts;
using RobinTTY.NordigenApiClient.Models.Errors;
using RobinTTY.NordigenApiClient.Models.Responses;

namespace RobinTTY.NordigenApiClient.Tests.Mocking;

public class AccountsEndpointTests
{
    [Test]
    public async Task Test()
    {
        var institutionsEndpointMock = new Mock<IInstitutionsEndpoint>();
        var institutionId = "some_id";
        institutionsEndpointMock.Setup(expression: s => s.GetInstitution(
            It.IsAny<string>(),
            It.IsAny<CancellationToken>())).ReturnsAsync(() =>
            new NordigenApiResponse<Institution, BasicError>(HttpStatusCode.OK, true,
                new Institution(institutionId, "NAME", "BIC", 90,
                    new List<string>(), new Uri("https://example.com")), null)
        );

        var response = await institutionsEndpointMock.Object.GetInstitution(institutionId);
        Assert.Multiple(() =>
        {
            Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK));
            Assert.That(response.Error, Is.Null);
            Assert.That(response.Result?.Id, Is.EqualTo(institutionId));
        });

       // Alternatively also using NordigenClient mock
        var nordigenClientMock = new Mock<INordigenClient>();
        nordigenClientMock.Setup(s => s.InstitutionsEndpoint).Returns(institutionsEndpointMock.Object);
        var response2 = await nordigenClientMock.Object.InstitutionsEndpoint.GetInstitution(institutionId);
        Assert.Multiple(() =>
        {
            Assert.That(response2.StatusCode, Is.EqualTo(HttpStatusCode.OK));
            Assert.That(response2.Error, Is.Null);
            Assert.That(response2.Result?.Id, Is.EqualTo(institutionId));
        });

    }
}

Amazing, all good now - thanks again for all of your work on this, you've been a massive help

Glad I could help :)