Reputation: 375
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:
What I am missing in _leadStorageMock.Setup().Returns() ?
Upvotes: 1
Views: 3159
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