AdSNobrega
AdSNobrega

Reputation: 23

Moq: setup a generic method with mocked parameters

I've been trying to write a few tests in NUnit for my generic methods, without success. I hope I can make my situation clear, since I had to heavily paraphrase my code.

DoBusinessLogic() is the method I want to test. It calls two other methods from the base class:

public class MySvc : BaseSvc, IMySvc
{
    private readonly IMyRepo _repo;
    private readonly IMyConnectorClass _connector
    public MySvc(IMyRepo repo) : base(repo)        
    {
        _repo = repo;
        _connector = _repo.GetConnector();
    }

    public async Task DoBusinessLogic(int id1, int id2){
        bool doesFirstObjectExist = await CheckMainObjExists<Foo>(id1, _connector);
        await CheckSubObjExists<Bar>(id2, _connector);
        // further business logic
    }
}

Those methods, in turn, call the same repository method, but have different logic after it:

public abstract class BaseSvc : IBaseSvc
{
    private readonly IBaseRepo _repo
    protected BaseSvc(IBaseRepo repo)
    {
        _repo = repo;
    }

    protected async Task<bool> CheckMainObjExists<T>(int? id, IMyConnectorClass connector)
    {
        return await _repo.GetObjectByIdAsync<T>(Id, connector) != null;
    }

    protected async Task CheckSubObjExists<T>(int? id, IMyConnectorClass connector)
    {
        if (await _repo.GetObjectByIdAsync<T>(Id, connector) == null)
            { throw new Exception("Object not found!"); }
    }
}

Next, I want to write unit a test for DoBusinessLogic() in the MySvc class. Unfortunately, it seems I can't mock the responses from the repository.

[TestFixture]
public class MySvcTests
{
    private MySvc _svc;
    private Mock<IMyRepo> _repoMock;
    private Mock<IMyConnectorClass> _connectorMock;

    [SetUp]
    public void SetUp()
    {
        _repoMock = new Mock<IMyRepo>() {};
        _connectorMock = new Mock<IMyConnectorClass>();

        _repo.SetUp(r => r.GetConnector()).Return(_connectorMock.Object);

        _svc = new MySvc(_repoMock);    
    }

    /* 
    My intent in this test, is to make CheckMainObjExists() pass,
    but make CheckSubObjExist() fail.
    */
    [Test]
    public async Task DoBusinessLogic_If2ndObjectNotExist_ThrowException()
    {
        // This should return an object
        _repoMock.Setup(r => r.GetObjectByIdAsync<Foo>(It.IsAny<int>(), _connectorMock.Object))
            .ReturnsAsync(new Foo());

        // This should return null
        _repoMock.Setup(r => r.GetObjectByIdAsync<Bar>(It.IsAny<int>(), _connectorMock.Object))
            .ReturnsAsync((Bar) null);

        Assert.Throws<Exception>(await _svc.DoBusinessLogic());
    }
}

However, when I run the test, both methods that I set up for my mock repo return null, whereas I expect a "true" from the first. I do not know where the problem is situated, but I have my suspicions:

  1. Is it possible to setup a method, using a mocked object as a parameter? In this case, is it possible to use _connectorMock.Object as a setup parameter?
  2. Is it possible to setup the same generic method multiple times, but for a different type each time? It's first setup for Foo, then for Bar.

Upvotes: 0

Views: 3909

Answers (1)

Nkosi
Nkosi

Reputation: 247443

I just tested this code and it runs as expected. Now I had to make a lot of assumptions just to get the code to compile and run which would mean that my test of your code is flawed as I may have fixed something that was omitted in your example.

I made no changes to your test setup code, which worked.

[TestClass]
public class MySvcTests {
    [TestMethod]
    [ExpectedException(typeof(Exception))]
    public async Task DoBusinessLogic_If2ndObjectNotExist_ThrowException() {
        var _repoMock = new Mock<IMyRepo>() { };
        var _connectorMock = new Mock<IMyConnectorClass>();

        _repoMock.Setup(r => r.GetConnector()).Returns(_connectorMock.Object);

        var _svc = new MySvc(_repoMock.Object);
        // This should return an object
        _repoMock.Setup(r => r.GetObjectByIdAsync<Foo>(It.IsAny<int>(), _connectorMock.Object))
            .ReturnsAsync(new Foo());

        // This should return null
        _repoMock.Setup(r => r.GetObjectByIdAsync<Bar>(It.IsAny<int>(), _connectorMock.Object))
            .ReturnsAsync((Bar)null);

        await _svc.DoBusinessLogic(0, 0);
    }
}

Upvotes: 0

Related Questions