Когда я отправил этот вопрос, я упустил несколько важных деталей. Я хочу подробно рассказать о том, что на самом деле происходило, если это поможет кому-то в будущем. Мой тестируемый метод фактически очищал список, который передавался в макет:
public class SomeDTO { }
public class SomeRepository
{
public virtual void Write(IEnumerable<SomeDTO> list) { }
}
public class MainClass
{
private readonly SomeRepository _someRepository;
private readonly List<SomeDTO> _testList = new List<SomeDTO>();
public MainClass(SomeRepository someRepository)
{
_someRepository = someRepository;
}
public void DoRepositoryWrite()
{
_testList.AddRange(Enumerable.Repeat(new SomeDTO(), 25));
_someRepository.Write(_testList);
_testList.Clear();
}
}
class Program
{
static void Main(string[] args)
{
var mockSomeRepository = new Mock<SomeRepository>();
var mainClass = new MainClass(mockSomeRepository.Object);
mainClass.DoRepositoryWrite();
mockSomeRepository.Verify(m => m.Write(It.IsAny<IEnumerable<SomeDTO>>()), Times.Once(), "Write was not called");
mockSomeRepository.Verify(m => m.Write(It.Is<IEnumerable<SomeDTO>>(l => l.Count() == 25)), Times.Once(), "Write was not called with a 25-element-list");
}
}
Оглядываясь назад, я думаю, что это немного очевидно, но для меня здесь важно то, что насмешка держится за ссылку на список, который был передан. Поэтому вы должны быть осторожны с любыми побочными эффектами, которые изменяют этот список.
Чтобы правильно написать тест, мне нужно было сразу проверить свойства переданного списка при вызове проверяемого метода. Для этого я использовал обратный вызов:
int listCountAtTimeOfCall = 0;
mockSomeRepository.Setup(
m => m.Write(It.IsAny<IEnumerable<SomeDTO>>())).Callback
<IEnumerable<SomeDTO>>(list => listCountAtTimeOfCall = list.Count());
... do the work ...
Assert.AreEqual(listCountAtTimeOfCall, 25);