Передача объекта с заданными парметрами в методы mock-объектов

Рейтинг: 0Ответов: 1Опубликовано: 25.04.2023

Ситуация такая. У меня есть подлежащий тестированию метод "МетодДляТеста" внутри класса "КлассДляТеста". Внутри метода "МетодДляТеста" создаётся объект (назову его О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, будет достаточно сложно понять из результата теста, какие поля объекта не соответствуют ожидаемым значениям.