Lightning3
Lightning3

Reputation: 375

UnitTests - Moq - How to Return() object from Moq that matches Parameter in Setup()

I am confused in Moq and I'm not sure what is wrong here.

I would like to test LeadService that depend on ILeadStorageService, and I want to configure Moq in that way - Return the object that matches the GUID passed in Setup.

The problem is in the Moq Setup/Returns line, because when I substitute dependent object to its real instantiation - Test passes, but it is totally wrong. I don't want to test just LeadService, not a dependent storage.

    public LeadService( IConfigurationDbContext configurationDbContext, 
                        ILeadStorageService leadStorageService,
                        ILeadDeliveryService deliveryService)
    {
        this.configurationDbContext = configurationDbContext;
        this.leadStorageService = leadStorageService;
        this.deliveryService = deliveryService;

    }

Tested method

        public TestLeadResponse ProcessTestLead(TestLeadRequest request)
        {
        var response = new TestLeadResponse()
        {
            Status = TestLeadStatus.Ok
        };

        try
        {
            var lead = leadStorageService.Get(request.LeadId);
            if (lead == null)
            {
                throw new LeadNotFoundException(request.LeadId);
            }

            var buyerContract =
                configurationDbContext.BuyerContracts.SingleOrDefault(bc => bc.Id == request.BuyerContractId);
            if (buyerContract == null)
            {
                throw new BuyerContractNotFoundException(request.BuyerContractId);
            }

            response.DeliveryEntry = deliveryService.DeliverLead(lead, buyerContract);
        }
        catch (LeadNotFoundException e)
        {
            response.Status = TestLeadStatus.LeadNotFound;
            response.StatusDescription = e.Message;
        }
        catch (BuyerContractNotFoundException e)
        {
            response.Status = TestLeadStatus.BuyerContractNotFound;
            response.StatusDescription = e.Message;
        }

        return response;
    }

then in test preparation:

    [TestInitialize]
    public void Initialize()
    {
        _leadIdStr = "2c3ac0c0-f0c2-4eb0-a55e-600ae3ada221";

        _dbcontext = new ConfigurationDbContext();
        _lead = PrepareLeadObject();
        _buyerContract = PrepareBuyerContractObject(Id : 1, BuyerContractId :  1, BuyerTag: "GAME");
        _leadDeliveryMock = new Mock<ILeadDeliveryService>();
        _leadStorageMock = new Mock<ILeadStorageService>();
        _leadStorageService = new LeadStorageService("LeadGeneration_Dev");

    }

    private Lead PrepareLeadObject()
    {
        var lead = new Lead() {CountryId = 1, Country = "NL", Id = Guid.Parse(_leadIdStr)};
        return lead;

    }

and the test itself:

    [TestMethod]
    public void LeadServiceTest_ProcessTestLeadWithWrongBuyerContractIDThrowsBuyerContractNotFoundException()
    {
        _leadDeliveryMock
            .Setup(methodCall => methodCall.DeliverLead(_lead, _buyerContract))
            .Returns<LeadDeliveryEntry>((r) => PrepareLeadDeliveryEntry());

        // here is the problem!!!!
        _leadStorageMock.Setup(mc => mc.Get(_leadId)).Returns((Lead l) => PrepareLeadObject());

        //if i change to real object - test passes
        //_service = new LeadService(_dbcontext, _leadStorageService, _leadDeliveryMock.Object);
        _service = new LeadService(_dbcontext, _leadStorageMock.Object, _leadDeliveryMock.Object);

        var response = _service.ProcessTestLead(new TestLeadRequest() { BuyerContractId = int.MaxValue, LeadId = _leadId });

        Assert.IsNotNull(response);
        Assert.AreEqual(response.Status, TestLeadStatus.BuyerContractNotFound);

    }

Instead of expected Return - I got an exception: ArgumentException

What I am missing in _leadStorageMock.Setup().Returns() ?

Upvotes: 1

Views: 3159

Answers (1)

Sergey Berezovskiy
Sergey Berezovskiy

Reputation: 236208

The Returns extension method accepts a delegate with same parameters as the method which you are mocking. And those arguments will be passed to delegate during invocation of mocked method. So instead of Lead object, you will get the argument which is passed to mc.Get method - the lead id:

_leadStorageMock.Setup(mc => mc.Get(_leadId))
      .Returns((Guid leadId) => PrepareLeadObject());

Check QuickStart section related to accessing invocation arguments when returning a value.


Note that there is a bunch of Returns extension methods which accept a value function as an argument:

Returns<T>(Func<T, TResult> valueFunction);
Returns<T1, T2>(Func<T1, T2, TResult> valueFunction);
Returns<T1, T2, T3>(Func<T1, T2, T3, TResult> valueFunction);
// etc

As you can see those value functions calculate the value to return from mocked method, but they all receive a different number of arguments (up to 16). Those arguments will exactly match arguments of the method you are mocking and they will be passed to the valueFunction during invocation of the mocked method. So if you are mocking some function with two arguments, then corresponding extension should be used:

mock.Setup(m => m.Activate(It.IsAny<int>(), It.IsAny<bool>())
    .Returns((int i, bool b) => b ? i : 0); // Func<T1, T2, TResult>

Upvotes: 6

Related Questions