MOQ配置疑问:如何正确设置MockRepository返回过滤后的IEnumerable<T>
FindBy Method Filtering Hey there! Let's break down why your Moq setup isn't working as expected and fix it step by step.
The Core Issue: Incorrect Setup Matching
Your main problem lies in how you're setting up the FindBy method on your mock repository. Looking at your code:
mockRepository.Setup(x => x.FindBy(x => It.IsAny<IAsyncRepository<Task>>())) .Returns(System.Threading.Tasks.Task.FromResult((IEnumerable<Task>)tasks.Where(x => x.Id == taskCurr.Id)));
This setup is trying to match a FindBy call that takes an IAsyncRepository<Task> parameter—but your actual FindBy method (used in the handler) accepts an Expression<Func<Task, bool>> (the filter lambda x => x.Id != command.Id). Since the parameter types don't match, Moq ignores this setup entirely:
- When you use
SetReturnsDefault, it falls back to returning the full list of tasks every time. - When you only use this incorrect setup, Moq can't find a matching setup and returns an empty list by default.
The Correct Setup Approach
Instead of hardcoding a filter in your setup, you need to tell Moq to apply the incoming filter expression to your test task list. This way, whatever lambda the handler passes to FindBy will actually filter the tasks you've created.
Replace your mock setup with this:
// Setup FindBy to apply the incoming predicate to your test tasks mockRepository.Setup(repo => repo.FindBy(It.IsAny<Expression<Func<Task, bool>>>())) .ReturnsAsync((Expression<Func<Task, bool>> predicate) => tasks.Where(predicate.Compile()).ToList());
What This Does:
It.IsAny<Expression<Func<Task, bool>>>()tells Moq to match any call toFindBythat passes a filter lambda.ReturnsAsynctakes the incoming predicate, compiles it into a usable function, and applies it to yourtaskslist—returning only the items that match the filter.
Why This Fixes Your Test
When your handler runs this line:
var tasks = await _repository.FindBy(x => x.Id != command.Id);
Moq will take the x => x.Id != command.Id predicate, apply it to your tasks list (which contains taskPrev and taskCurr), and return only taskCurr (since its ID is taskId + 1, not equal to command.Id which is taskId). This means the subsequent overlap check won't find any conflicting tasks, so the BusinessException won't be thrown, and your test will pass.
Additional Tips
- Remove
SetReturnsDefaultfor the repository: Now that your setup correctly handles any filter, you don't need a default return value—it will override your specific setup anyway. - Use
ReturnsAsyncinstead ofTask.FromResult: It's cleaner and designed for async methods in Moq. - Ensure Task properties are accessible: Make sure the
Id,TimeSheetId,Date, etc., properties on yourTaskclass arepublic—otherwise, the compiled predicate won't be able to read them correctly.
Final Corrected Mock Setup Snippet
Here's how that section of your test should look after the fix:
//Criar 1ª Task Task taskPrev = new Task(taskTypeId, projectId, costCenterId, Convert.ToDateTime(date), startHour, startMinute, endHour, endMinute, workItem, timeSheetId, description); taskPrev.Id = taskId; //Criar 2ª Task Task taskCurr = new Task(taskTypeId, projectId, costCenterId, Convert.ToDateTime(date), endHour, startMinute, Convert.ToByte(endHour + 1), endMinute, workItem, timeSheetId, description); taskCurr.Id = taskId + 1; List<Task> tasks = new List<Task>(); tasks.Add(taskPrev); tasks.Add(taskCurr); // Correct Moq setup for FindBy mockRepository.Setup(repo => repo.FindBy(It.IsAny<Expression<Func<Task, bool>>>())) .ReturnsAsync((Expression<Func<Task, bool>> predicate) => tasks.Where(predicate.Compile()).ToList());
内容的提问来源于stack exchange,提问作者Yasmin Kruger

