Передача объекта с заданными парметрами в методы mock-объектов
Ситуация такая. У меня есть подлежащий тестированию метод "МетодДляТеста" внутри класса "КлассДляТеста". Внутри метода "МетодДляТеста" создаётся объект (назову его О1) некоего класса с определёнными параметрами. Затем в этом же тестируемом методе объект О1 передаётся в метод другого объекта "Репозиторий" (который является полем класса "КлассДляТеста", содержащего тестируемый метод "МетодДляТеста").
Метод для теста:
public class ClassForTest
{
private readonly SomeRepository _repository;
public ClassForTest(SomeRepository repository)
{
_repository = repository;
}
public OperationResult MethodForTest(int id, int otherInfo)
{
if(otherInfo < 0)
return OperationResult.NoSuccess
SomeObject someObject = new()
{
Id = id,
Data = otherInfo * 10
};
_repository.DoSomething(someObject);
return OperationResult.Success;
}
}
Как протестировать данный метод, чтобы someObject создавался именно с такими параметрами и передавался в метод DoSomething. Если просто передавать созданный заранее объект из вне в mock метода DoSomething, то при проверке количества срабатываний он покажет 0.
Вот что я пробовал:
private readonly Mock<SomeRepository>_repositoryMock = new Mock<SomeRepository>();
[Fact]
public void AddRegionGroupAsyncTest()
{
//Arrange
int id = 10;
int otherInfo = 10;
var someObject = new SomeObject { Id = 10, Data = 100 };
_repositoryMock.Setup(x => x.DoSomething(someObject));
var sut = new ClassUnderTest(_repositoryMock.Object);
//Act
sut.MethodForTest(id, otherInfo);
//Assert
_repositoryMock.Verify(x => x.DoSomething(someObject), Times.Once());
}
В итоге Verify не срабатывает, так метод DoSomething вызывается с другим объектом, который создавался внутри метода MethodForTest. То есть они с разными ссылками. Есть ли способ решить данную проблему без переопределения Equal объекта SomeObject?
Ответы (1 шт):
Когда нужно проверить, что в замоканный метод передается определенный объект, я обычно создаю callback на вызов этого метода, и считываю полученный в callback объект во внешнюю переменную, а затем уже проверяю эту переменную с ожидаемым значением. Выглядит это примерно так
[Fact]
public void AddRegionGroupAsyncTest()
{
//Arrange
int id = 10;
int otherInfo = 10;
var expectedObject = new SomeObject { Id = 10, Data = 100 };
SomeObject actualObject = null;
var repositoryMock = new Mock<SomeRepository>();
repositoryMock
.Setup(x => x.DoSomething(It.IsAny<SomeObject>()))
.Callback<SomeObject>((obj) => actualObject = obj);
var sut = new ClassForTest(repositoryMock.Object);
//Act
sut.MethodForTest(id, otherInfo);
//Assert
repositoryMock.Verify(x => x.DoSomething(It.IsAny<SomeObject>()), Times.Once());
AssertSomeObjectsAreEqual(expectedObject, actualObject);
}
private void AssertSomeObjectsAreEqual(SomeObject expected, SomeObject actual)
{
Assert.Equal(expected.Id, actual.Id);
Assert.Equal(expected.Data, actual.Data);
}
В этом способе можно сделать assert объектов прямо внутри callback, но это уже дело вкуса.
Также существует другой способ проверки аргументов вызванного мок-метода - через Verify без использования callback. Будет выглядеть вот так:
[Fact]
public void AddRegionGroupAsyncTest()
{
//Arrange
int id = 10;
int otherInfo = 10;
var expectedObject = new SomeObject { Id = 10, Data = 100 };
var repositoryMock = new Mock<SomeRepository>();
repositoryMock.Setup(x => x.DoSomething(It.IsAny<SomeObject>()));
var sut = new ClassForTest(repositoryMock.Object);
//Act
sut.MethodForTest(id, otherInfo);
//Assert
repositoryMock.Verify(
x => x.DoSomething(
It.Is<SomeObject>(
actualObject => AreObjectsEqual(expectedObject, actualObject)
)
),
Times.Once()
);
}
private bool AreObjectsEqual(SomeObject expected, SomeObject actual)
{
return expected.Id == actual.Id
&& expected.Data == actual.Data;
}
Минус такого подхода в том, что если SomeObject имеет много полей, то в случае если тест провалится на Verify, будет достаточно сложно понять из результата теста, какие поля объекта не соответствуют ожидаемым значениям.