I'm new to Unit Testing, and Moq, so if I'm completely wrong in my method or understanding, please assist me.
I have a logic method I am testing. I've commented out the logic, but all it does is check a few values in the 'model' and returns if there's a problem. In the case we're looking at, there's no problem.
public ReplyDto SaveSettings(SnowballDto model)
{
// Some logic here that reads from the model.
var result = _data.SaveSettings(model);
return result;
}
My test, using NUnit and MOQ, looks like this:
_logic = new SnowballLogic(mockSnowballData.Object, mockLog.Object);
mockSnowballData
.Setup(x => x.SaveSettings(SnowballDto_Good))
.Returns(new ReplyDto {
IsSuccess = true,
Message = "Saved",
ReplyKeyID = 1
});
In each test, I call a private setup function that sets up the things I'll be using.
private void SetupData()
{
SnowballDto_Good = new SnowballDto {
FirstPaymentDate = DateTime.UtcNow,
ID = 1,
OrderedIDPriority = new List<int>(),
SnowballTypeID = 1,
TargetPayment = 1000
};
DebtDtoList_ThreeDebt.Clear();
DebtDtoList_ThreeDebt.Add(new DebtDto { ID = 1, Description = "Debt 1", ManualSnowballPriority = 1, MinimumMonthlyPaymentAmount = 140, OpeningBalance = 5000, RunningData = new DebtRunningDto { Balance = 5000 }, OpeningDate = DateTime.UtcNow, SnowballID = 1, StandardRate = 10 });
DebtDtoList_ThreeDebt.Add(new DebtDto { ID = 2, Description = "Debt 2", ManualSnowballPriority = 2, MinimumMonthlyPaymentAmount = 90, OpeningBalance = 1600, RunningData = new DebtRunningDto { Balance = 1600 }, OpeningDate = DateTime.UtcNow, SnowballID = 1, StandardRate = 15 });
DebtDtoList_ThreeDebt.Add(new DebtDto { ID = 3, Description = "Debt 3", ManualSnowballPriority = 3, MinimumMonthlyPaymentAmount = 300, OpeningBalance = 9000, RunningData = new DebtRunningDto { Balance = 9000 }, OpeningDate = DateTime.UtcNow, SnowballID = 1, StandardRate = 20 });
}
So, my understanding of MOQ is that I am saying "When the SnowballData class's "SaveSettings" methid is called, and a "SnowballDto_Good" object is passed in, always return a new ReplyDto with IsSuccess = true.
Therefore, when I make the call:
var result = _data.SaveSettings(model);
It should return ReplyDto with IsSuccess = true
However, when I put a breakpoint in when I call 'SaveSettings', it returns null all the time.
If I change my setup to:
.Setup(x => x.SaveSettings(It.IsAny<SnowballDto>()))
The test passes. Why is it returning null when I give it a real SnowballDto?
well, it seems you are passing an instance of SnowballDto named model in the "act" part of your test
var result = _data.SaveSettings(model);
In the setup of moq, however, you configure it to return a new ReplyDto only when the instance SnowballDto_Good is specified to SaveSettings. In all other cases, the method's mock is not configured, and - in case of a loose mocking strategy (the default) - it will return the default value for SaveSettings return type. In this case: null.
When you're using It.IsAny<SnowballDto> you are basically telling moq to configure SaveSettings to return a new instance not only then the instance SnowballDto_Good is passed to it, but any instance of that type.
What you need to do is to change your "act" part of your test as follows:
var result = _data.SaveSettings(SnowballDto_Good);
then it will work with your original mock setup, because the correct instance will be passed to SaveSettings.
This is precisely why I like to instantiate my mocks using MockBehavior.Strict.
Instead of returning null, it will throw an exception that will tell you that you didn't configure your mock correctly.
Hope this helps.
Related
I've added some unit tests to an ASP.NET MVC app I've written using .NET 6. I'm using an in-memory database for the unit tests. One of the unit tests fails with the following error:
Xunit.Sdk.IsAssignableFromException HResult=0x80131500
Message=Assert.IsAssignableFrom() Failure Expected:
typeof(System.Collections.Generic.List<PharmacyWarehouseV2.Models.SiteToSite>)
Actual: (null) Source=xunit.assert StackTrace: at
Xunit.Assert.IsAssignableFrom(Type expectedType, Object object) at
Xunit.Assert.IsAssignableFrom[T](Object object) at
PharmacyWarehouseV2.Test.SiteToSiteControllerTest.IndexTest() in
D:\Repos\PW\PharmacyWarehouseV2\PharmacyWarehouseV2.Test\SiteToSiteControllerTest.cs:line
29
The strange thing is I've got another unit test I wrote in the same way, which works fine.
Here is the code for the in-memory data I'm using with Moq (note: the SiteToSite class is large, so I'll be removing most of the properties for brevity's sake. The only properties which are required are the first two):
public static IEnumerable<SiteToSite?> GetSiteToSites()
{
// Construct some SiteToSites first
var siteToSites = new List<SiteToSite>()
{
new SiteToSite()
{
SiteToSiteID = 1,
DateReceived = new DateTime(2020, 7, 1),
OrderedByName = "John Doe",
OrderedByID = 1,
// other properties removed for brevity
},
new SiteToSite()
{
SiteToSiteID = 2,
DateReceived = new DateTime(2021, 3, 1),
OrderedByName = "Teresa",
OrderedByID = 2,
// other properties removed for brevity
}
};
// Now construct SiteToSiteItems
var ss1 = new SiteToSiteItem()
{
SiteToSiteItemID = 1,
SiteToSiteID = 1,
ProgramName = "Program One",
Notes = "First note"
};
var ss2 = new SiteToSiteItem()
{
SiteToSiteItemID = 2,
SiteToSiteID = 2,
ProgramName = "Program Two",
Notes = "Second note"
};
var ss3 = new SiteToSiteItem()
{
SiteToSiteItemID = 3,
SiteToSiteID = 2,
ProgramName = "Program Two",
Notes = "Third note"
};
// Now assing SiteToSiteItems to their parent SiteToSites
siteToSites[0].SiteToSiteItems = new List<SiteToSiteItem>() { ss1 };
siteToSites[1].SiteToSiteItems = new List<SiteToSiteItem>() { ss2, ss3 };
return siteToSites;
}
I use a service/repository class. This is the method that is used in the unit test:
public IEnumerable<SiteToSite?> GetAll()
{
var tmp = _context.SiteToSite.OrderBy(s => s.SiteToSiteID);
return tmp;
}
And here's the unit test that's failing:
[Fact]
public void IndexTest()
{
// arrange
var mockRepo = new Mock<ISiteToSiteService>();
mockRepo.Setup(m => m.GetAll()).Returns(SiteToSiteMockData.GetSiteToSites());
var emptyDbContext = new PharmacyDBContext(); //won't test the AJAX calls
var controller = new SiteToSiteController(emptyDbContext, mockRepo.Object);
// act
var result = controller.Index();
// assert
Assert.NotNull(result);
var viewResult = Assert.IsType<ViewResult>(result);
Assert.True(viewResult.ViewData.Count > 0, "viewResult does not have any records, as it should");
var viewResultSites = Assert.IsAssignableFrom<List<SiteToSite>>(viewResult.ViewData.Model);
if (viewResultSites.Count > 0)
{
// NOTE: I do not like this; it violates unit testing.
Assert.Equal(2, viewResultSites.Count);
Assert.Equal("John Doe", viewResultSites[0]?.OrderedByName);
}
}
When I debug the test, after the result variable is assigned in the "act" step, it does have the data from in-memory. However, the viewResult.ViewData.Model is null. I don't understand how result has data, but viewResult.ViewData.Model doesn't. I've gone to the xUnit repo on GitHub to look at the documentation, but it wasn't clear to me what the problem is. What might be causing the discrepancy?
Addendum 1
Here's the GetAll() method from the SiteToSiteService:
public IEnumerable<SiteToSite?> GetAll()
{
var tmp = _context.SiteToSite.OrderBy(s => s.SiteToSiteID);
return tmp;
}
And here's the Index() method from the controller:
public IActionResult Index()
{
return View();
}
(Posting answer on behalf of the question author, to move it to the answer space).
After Peter's question I realized I made a mistake. The Index action method didn't pass any data into the view. This is what was giving me a null. (For business reasons we don't show a list of site-to-sites.) So, I've put an xUnit [Fact(Skip = "<message>")] into the unit test for the IndexTest.
However, I do have a DetailTest unit test. Here's the code for the DetailTest:
[Fact]
public void DetailsTest()
{
// arrange
var mockRepo = new Mock<ISiteToSiteService>();
mockRepo.Setup(m => m.Get(1)).Returns(SiteToSiteMockData.Get(1));
var controller = new SiteToSiteController(new PharmacyDBContext(), mockRepo.Object);
// act
var result = controller.GetSiteToSiteById(1);
// assert
var viewResult = Assert.IsType<ViewResult>(result);
var viewResultSiteToSite = Assert.IsAssignableFrom<SiteToSite>(viewResult.ViewData.Model);
Assert.Equal("John Doe", viewResultSiteToSite.OrderedByName);
}
Here's the action method from the controller:
public ActionResult GetSiteToSiteById(int id)
{
var site2Site = siteToSiteService.Get(id);
if (site2Site == null)
{
return NotFound();
}
return View("SiteToSiteDetail", site2Site);
}
and here's the Get(int id) method from the service/repository method:
public SiteToSite? Get(int id)
{
var result = _context.SiteToSite.Include(i => i.SiteToSiteItems).Where(s => s.SiteToSiteID == id);
return result.FirstOrDefault();
}
while interface mocking I am trying to send with actual inputs in model object, but getting failure with
Moq.MockException : ISearchRepo.GetEsDataWithBoolByAggregation<IndexVM>(BoolMustMatchAggregationIM) invocation failed with mock behavior Strict.
All invocations on the mock must have a corresponding setup.
error message.
Mock method for interface input object (which is sending same like in manual)
private BoolMustMatchAggregationIM GetBoolMustMatchAggregationIMMockData()
{
var objInputs = GetTopologyTabularApplyFilterMockData();
var studentFieldValue = objInputs.StudentId != null ? string.Join(',', objInputs.StudentId) : string.Empty;
BoolMustMatchAggregationIM topoplogyApplyFilter = new BoolMustMatchAggregationIM()
{
From = objInputs.From,
Size = objInputs.Size,
IndexName = ElasticSearchIndexConstant.Index,
FirstField = DataConstant.MarketField,
FirstFieldValue = objInputs.Market,
SecondField = DataConstant.StudentField,
SecondFieldValue = studentFieldValue,
ThirdField = DataConstant.SubjectField,
ThirdFieldValue = objInputs.SubjectId != null ? objInputs.SubjectId : null,
FourthField = DataConstant.SiteIdFieldName,
FourthFieldValue = objInputs.SiteId != null ? objInputs.SiteId[0] : null,
OperatorType = DataConstant.NestAndOperator,
CardinalField = DataConstant.CardinalField,
SumName = DataConstant.SumName,
MinimumMatch = DataConstant.One
};
return topoplogyApplyFilter;
}
Mock method for output object from interface
private IEnumerable<SiteSubjectIndexVM> GetSiteSubjectIndexClassMockData()
{
List<SiteSubjectDetails> objListSiteSubject = new List<SiteSubjectDetails>();
SiteSubjectDetails objSiteSubject = new SiteSubjectDetails { SubjectId = 4002453 };
objListSiteSubject.Add(objSiteSubject);
List<SiteSubjectIndexVM> objListSiteSubjectIndexClass = new List<SiteSubjectIndexVM>();
SiteSubjectIndexVM objSiteSubjectIndexClass = new SiteSubjectIndexVM()
{
Id = 123,
StudentId = "SE123",
Longitude = -122.51m,
Latitude = 47.66m,
Region = "Selected",
SiteId = "SE03D123",
HasSubjectSites = 1,
Market = "HNK",
TimeStamp = new DateTime(2022, 08, 07, 11, 02, 51, 167),
AAVStatus = "Selected"
};
objListSiteSubjectIndexClass.Add(objSiteSubjectIndexClass);
objSiteSubjectIndexClass = new SiteSubjectIndexVM()
{
Id = 456,
SiteId = "SE04D456",
Subjects = objListSiteSubject,
StudentId = "SE456",
Latitude = 47.74m,
Longitude = -122.15m,
Market = "WGL",
TimeStamp = new DateTime(2022, 08, 07, 11, 02, 51, 167),
Region = "WEST",
HasSubjectSites = 1
};
objListSiteSubjectIndexClass.Add(objSiteSubjectIndexClass);
return objListSiteSubjectIndexClass;
}
Test Case
[Fact]
public async Task GetTabularApplyFilterAsync_WhenMarketIsNotNull_ReturnsSiteSubjectsIndexFromES()
{
//Arrange
var objRepoIM = GetBoolMustMatchAggregationIMMockData();
var objRepoVM = GetSiteSubjectIndexClassMockData().Where(x=>x.Id== 72337696).ToList();
var objIMapperVM = GetSiteSubjectIndexMockData().Where(x => x.Id == 72337696).ToList();
var objServiceIM = GetTabularApplyFilterMockData();
objServiceIM.StudentId = null; objServiceIM.SubjectId = null; objServiceIM.SiteId = null;
//Action
_mockISearchRepo.Setup(x => x.GetEsDataWithBoolByAggregationAsync<SiteSubjectIndexVM>(objRepoIM)).ReturnsAsync(objRepoVM);
_mockMapper.Setup(x => x.Map<IEnumerable<SiteSubjectIndex>>(objRepoVM)).Returns(objIMapperVM);
var subjectService = this.CreateSearchService();
var result = await subjectService.GetTabularApplyFilterAsync(objServiceIM).ConfigureAwait(false);
//Assert
Assert.NotNull(result);
Assert.Equal(result, objIMapperVM);
_mockRepository.VerifyAll();
}
by _mockIElasticSearchRepo.Setup(x => x.GetEsDataWithBoolByAggregationAsync<SiteLinkIndexVM>(It.IsAny<BoolMustMatchAggregationIM>())).ReturnsAsync(objRepoVM);
error get resolved, but why? where I have given correct input model with correct values.
The error message signals that there is no setup for the call. The following setup will be met, if the parameter is equal to the object that you created:
_mockISearchRepo.Setup(x => x.GetEsDataWithBoolByAggregationAsync<SiteSubjectIndexVM>(objRepoIM)).ReturnsAsync(objRepoVM);
Most likely, the call to GetEsDataWithBoolByAggregationAsync in the code under test does not use the exact same instance that you created in your test, but creates an instance of its own. Hence check will fail because it is not the same object and the setup is not matched.
You can work around this by defining the setup so that you check the parameters dynamically, e.g.
_mockISearchRepo
.Setup(x => x.GetEsDataWithBoolByAggregationAsync<SiteSubjectIndexVM>(It.Is<BoolMustMatchAggregationIM>(y => y.IndexName == ElasticSearchIndexConstant.Index))
.ReturnsAsync(objRepoVM);
In above sample, I have only included a very simple check for the index name. You can extend the expression to check the input more thoroughly.
By using this approach, the setup will be matched as soon as the expression returns true so this also works if the code under test creates the instance that is used as the parameter.
I know there are similar questions but somehow I am not able to figure out the situation in my case. I am getting Paramater count mismatch exception.
Here is how I am registering my Mock,
var couponService =
DependencyResolver.Resolve<Mock<ICouponWebServiceAdapter>>();
couponService.Setup(a =>
a.checkCouponAvailability(It.IsAny<orderLine[]>(),
It.IsAny<orderHeader>()))
.Returns((couponDetail[] request) =>
{
var coupon = new couponDetail
{
description = "75% off the original price",
value = 50
};
var coupon1 = new couponDetail
{
description = "500 off the original price",
value = 20
};
var coupondetails = new couponDetail[] { coupon, coupon1 };
return coupondetails;
});
the checkCouponAvailability is returning couponDetail[]
What am I doing wrong? I tried putting my return as IQueryable
It appears that inside of the Returns method you are specifying a parameter called request of type couponDetail[], yet the method itself takes the parameters of (orderLine[], orderHeader). The method that is passed into Returns gets invoked with the actual parameters that are passed into your mocked method which will cause the ParameterCountMismatchException you are getting.
You can pass in the literal object you want by mocking your return before mocking the function. Example below:
var coupondetails = new couponDetail[] {
new couponDetail
{
description = "75% off the original price",
value = 50
},
new couponDetail
{
description = "500 off the original price",
value = 20
}
};
var couponService = DependencyResolver.Resolve<Mock<ICouponWebServiceAdapter>>();
couponService
.Setup(a => a.checkCouponAvailability(It.IsAny<orderLine[]>(), It.IsAny<orderHeader>()))
.Returns(coupondetails);
You can pass a method to returns which MUST take all of the arguments passed into the original method. Example below:
var couponService = DependencyResolver.Resolve<Mock<ICouponWebServiceAdapter>>();
couponService
.Setup(a => a.checkCouponAvailability(It.IsAny<orderLine[]>(), It.IsAny<orderHeader>()))
.Returns((orderLine[] arg1, orderHeader arg2) => {
return new couponDetail[] {
new couponDetail
{
description = "75% off the original price",
value = 50
},
new couponDetail
{
description = "500 off the original price",
value = 20
}
};
});
I implement unit tests for the engine and setup 2 different methods in the repository mock. So the first one works well, but when I do linq Select for the second one it returns 0, whereas I did setup to return specific object.
My code in Engine:
private readonly IEmployerWorkersClient _employerWorkersClient;
private readonly IJobsClient _jobsClient;
public EmployerWorkerEngine(IEmployerWorkersClient employerWorkersClient, IJobsClient jobsClient,)
{
_employerWorkersClient = employerWorkersClient;
_jobsClient = jobsClient;
}
public async Task<Grid<WorkerFiltered>> GetWorkersAsync(int employerId, GridState gridState)
{
var employerWorkers = await _employerWorkersClient.GetEmployerWorkersByEmployerIdAsync(employerId);
int? payrollId = null;
int? jobRoleId = null;
DateTime? bookedStart = null;
// !!!the following result is Empty collection!!!
List<JobRoleExtended> jobRoles = (await _jobsClient.GetJobRoleExtendedByEmployerWorkerIdsAsync(employerWorkers.Select(ew => ew.Id), payrollId, jobRoleId, bookedStart)).ToList();
// Other things
}
And hereafter my unit test class
private readonly EmployerWorkerEngine _employerWorkerEngine;
private readonly Mock<IEmployerWorkersClient> _employerWorkersClientMock;
private readonly Mock<IJobsClient> _jobClientMock;
public WorkersFilterTests()
{
_employerWorkersClientMock = new Mock<IEmployerWorkersClient>();
_jobClientMock = new Mock<IJobsClient>();
_employerWorkerEngine = new EmployerWorkerEngine(_employerWorkersClientMock.Object, _jobClientMock.Object,);
}
[Theory]
[InlineData(1)]
public async Task GetFilteredWorkersByEmployerIdSuccessSimple(int employerId)
{
// Arrange
const int employerWorkerId = 3;
var gridState = new GridState { Skip = 0, Take = 1 };
var employerWorkers = new List<EmployerWorker> { new EmployerWorker {EmployerId = 1, WorkerId = 2, Id = employerWorkerId} };
_employerWorkersClientMock.Setup(client => client.GetEmployerWorkersByEmployerIdAsync(employerId))
.ReturnsAsync(() => employerWorkers);
var jobRolesExtended = new List<JobRoleExtended>
{
new JobRoleExtended
{
EmployerWorkerId = employerWorkerId,
BookedStartDate = DateTime.UtcNow,
BookedEndDate = DateTime.UtcNow.AddDays(1),
Id = 5,
JobId = 8,
Name = "Job 5",
PayrollId = 10,
PayrollName = "Conduct"
}
};
_jobClientMock
.Setup(client => client.GetJobRoleExtendedByEmployerWorkerIdsAsync(employerWorkers.Select(ew => ew.Id), null, null, null))
.ReturnsAsync(() => jobRolesExtended);
}
So, during testing the following method returns Empty collection:
_jobsClient.GetJobRoleExtendedByEmployerWorkerIdsAsync() and I don't get why.
I assume that it related with the reference of the collection that generates linq Select, but even in this case, I don't know how to handle it for testing.
Can somebody help me with that?
does this help:
_jobClientMock.Setup(client => client.GetJobRoleExtendedByEmployerWorkerIdsAsync(It.IsAny<int>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<int>())
.ReturnsAsync(() => jobRolesExtended);
(do check if I got the types right)
if this is the case, then your missing something with your parameters.
With Moq you have to setup the expectations correctly or else when called the mock wont match and thus not perform as expected.
In the method under test you have
int? payrollId = null;
int? jobRoleId = null;
DateTime? bookedStart = null;
List<JobRoleExtended> jobRoles =
(await _jobsClient.GetJobRoleExtendedByEmployerWorkerIdsAsync(
employerWorkers.Select(ew => ew.Id), payrollId, jobRoleId, bookedStart)
)
.ToList();
The Select statement appears to be returning a collection of Ids (IEnumerable<int>) and you have already configured the first call correctly.
You now need to correctly configure the second call to expect that collection of int Ids
_jobClientMock
.Setup(_ => _.GetJobRoleExtendedByEmployerWorkerIdsAsync(
It.IsAny<IEnumerable<int>>(),
null,
null,
null)
)
.ReturnsAsync(() => jobRolesExtended);
I am trying to learn how this Repository works by disecting it. I am really lost so was hoping the community would help me out a bit. Please keep in mind I am new to MVC so don't tear me apart to much. The error I am getting is because I have invalid arguments. I think it is from my 3rd argument but I am not quite sure how to fix it.
ResponentRepository.GetRespondents() is the problem Notice below.
In my controller I have:
List<int> projectIdsToInclude = new List<int>();
projectIdsToInclude.Add(4);
List<int> statusesToInclude = new List<int>();
statusesToInclude.Add(1);
List<string> fieldsToInclude = new List<string>();
fieldsToInclude.Add("firstName");
IEnumerable<int> pID = projectIdsToInclude;
IEnumerable<string> fields = fieldsToInclude;
var respondents = RespondentRepository.GetRespondents(UserSession, pID, statusesToInclude, fields);
In the Respository it has:
public List<Respondent> GetRespondents(UserSessionData sessionData, IEnumerable<int> projectIdsToInclude, IEnumerable<RespondentStatus> statusesToInclude, IEnumerable<string> fieldsToInclude)
{
var request = new RespondentRequest
{
Options = new RequestOptions
{
Include = fieldsToInclude,
//OrderBy = new List<OrderBy> { new OrderBy { Direction = OrderByDirection.Descending, Field = Create.Name<Respondent>(r => r.LastActionDate) } },
Filter = new LogicExpression
{
ExpressionA = new InExpression<int>
{
Field = Create.Name<Respondent>(r => r.ProjectId),
Values = projectIdsToInclude.ToList()
},
ExpressionB = new InExpression<RespondentStatus>
{
Field = Create.Name<Respondent>(r => r.RecruitingStatus),
Values = statusesToInclude.ToList()
},
Operator = SqlOperator.And
},
}
};
return Get(request, sessionData).Data;
}
My Invalid Argument I think is coming from the argument that is IEnumerable. So when I follow RespondentStatus I see this:
public enum RespondentStatus : int
{
[Description("Not Contacted")]
NotContacted = 0,
[Description("Pre-Screened")]
PreScreened = 1,
[Description("On Hold")]
Hold = 2,
[Description("Initial Refusal")]
InitialRefusal = 3,
[Description("Qualified Refusal")]
QualifiedRefusal = 4,
[Description("Remove From Panel")]
RemoveFromPanel = 5,
Terminated = 6,
Complete = 7,
//Probably should never reuse '8' as a status since retiring "Approved" status
Approved = 8,
// Phone contact statuses
[Description("No Answer")]
NoAnswer = 30,
Busy = 31,
[Description("Left Message")]
LeftMessage = 32,
[Description("Call Back Later")]
CallBackLater = 33,
[Description("Non-working Number")]
NonWorkingNumber = 34,
}
How do I fix my code in the controller to pull respondents from this repository? I just want to return some respondents. I think if I can do that I can further dissect from there. Can someone explain what I should do to make this work or what I am missing?
statusesToInclude should be IEnumerable<RespondentStatus> You are expecting IEnumerable<RespondentStatus> in you method, but calling it with List<int>.
Either change your method signature or your calling code so that the two statusesToInclude variables match up and have the same type.
For example:
List<RespondentStatus> statusesToInclude = new List<RespondentStatus>();
statusesToInclude.Add((RespondentStatus)1);