NSubstitute не съответства на Linq Expression

Внедрявам клас заявка за модел на хранилище и тествам с помощта на NSubstitute.

Интерфейс на хранилището:

public interface IMyRepository
{
    IQueryable<T> Query<T>(Expression<Func<T, bool>> filter) where T : class;
}

Интерфейс на DateTimeProvider:

public interface IMyDateTimeProvider
{
    DateTime GetDateNow();
}

Интерфейс на приложението:

public interface IMyApplication
{
    List<Thing> GetThingsByQuery(int status);
}

Изпълнение на приложението:

public class MyApplication : IMyApplication
{
    private readonly IMyRepository myRepository;

    private readonly IMyDateTimeProvider myDateTimeProvider;

    public MyApplication(IMyRepository myRepository, IMyDateTimeProvider myDateTimeProvider)
    {
        this.myRepository = myRepository;
        this.myDateTimeProvider = myDateTimeProvider;
    }

    public List<Thing> GetThingsByQuery(int status)
    {
        var createdDate = this.myDateTimeProvider.GetDateNow();

        return this.myRepository.Query<Thing>(t => t.CreatedDate == createdDate && t.Status == status).ToList();
    }
}

Тест:

[TestClass]
public class ApplicationTest
{
    private IMyApplication myApplication;

    private IMyDateTimeProvider myDateTimeProvider;

    private IMyRepository myRepository;

    [TestMethod]
    public void QueriesRepository()
    {
        // Arrange
        var createdDate = new DateTime(2014, 1, 1);

        this.myDateTimeProvider.GetDateNow().Returns(createdDate);

        const int Status = 1;

        // Act
        this.myApplication.GetThingsByQuery(Status);

        // Assert
        this.myRepository.Received().Query<Thing>(t => t.CreatedDate == createdDate && t.Status == Status);
    }

    [TestInitialize]
    public void TestInitialize()
    {
        this.myRepository = Substitute.For<IMyRepository>();

        this.myDateTimeProvider = Substitute.For<IMyDateTimeProvider>();

        this.myApplication = new MyApplication(this.myRepository, this.myDateTimeProvider);
    }
}

Но тестът се проваля със следното съобщение:

NSubstitute.Exceptions.ReceivedCallsException: Expected to receive a call matching:
    Query<Thing>(t => ((t.CreatedDate == value(MySolution.Test.ApplicationTest+<>c__DisplayClass0).createdDate) AndAlso (t.Status == 1)))
Actually received no matching calls.
Received 1 non-matching call (non-matching arguments indicated with '*' characters):
    Query<Thing>(*t => ((t.CreatedDate == value(MySolution.Application.MyApplication+<>c__DisplayClass0).createdDate) AndAlso (t.Status == value(MySolution.Application.MyApplication+<>c__DisplayClass0).status))*)

DateTime и Status се анализират в value(), които са различни между приложението и теста.

Защо е това? Как мога да поправя това?


person Shevek    schedule 20.10.2014    source източник


Отговори (2)


За сложни изрази, ако често смятате, че е по-лесно да твърдите върху уловени аргументи, като използвате обратни извиквания, отколкото с Received() . (непълен) пример:

Expression<Func<Thing, bool>> receivedFilter receivedFilter = null;
myRepository.When(x => x.Query<Thing>(Arg.Any<...>))
  .Do(x => receivedQuery = x.Arg<Expression<Func<Thing, bool>>>());

След това потвърдете израза на заснетия филтър. Всъщност може да е по-просто просто да изпълните филтърната функция на израза (вижте напр. тук)

Func<Thing, bool> predicate = receivedFilter.Compile();
var matchingThing = new Thing 
  { CreatedDate = createdData, Status = Status };
// assert matching
predicate(matchingThing).Should().BeTrue();

// assert non.matching
predicate(nonMatchingThing).Should().BeFalse();

Този подход изглежда прави теста малко по-черна кутия, но това като цяло не е лошо нещо.

person mkoertgen    schedule 15.07.2015

Използва се инструментът за сравнение по подразбиране за равенство за израз (референтно равенство):

например изразът (t => t.CreatedDate == createdDate && t.Status == Status``) в:

this.myRepository.Received().Query<Thing>(t => t.CreatedDate == createdDate  
                                            && t.Status == Status          );

Е различен екземпляр от израза в:

return this.myRepository.Query<Thing>(t => t.CreatedDate == createdDate  
                                        && t.Status == status          ).ToList();

За да коригирате валидирането на този метод, обадете се на съвпадения на аргументи в NSubstitute.

Но като пример:

Func<Expression<Thing, bool>, bool> validator = 
    // TODO this needs to be written properly, based on the expression, 
    // not its string representation
    e => e.Body.ToString() == "t.CreatedDate == createdDate  
      && t.Status == Status"; 
this.myRepository.Received().Query<Thing>(Arg.Is<Expression<Thing, bool>>(validator));
person Rich O'Kelly    schedule 20.10.2014
comment
Не съм сигурен как да приложа това. Опитах this.myRepository.Received().Query<Thing>(t => t.CreatedDate == Arg.Any<DateTime>() && t.Status == Arg.Any<int>()); и this.myRepository.Received().Query<Thing>(t => t.CreatedDate == Arg.Is(createdDate) && t.Status == Arg.Is(Status));, но и двете все още използват value() и се провалят - person Shevek; 20.10.2014
comment
Не можах да компилирам вашия пример. Това се компилира, но тестът се проваля с абсолютно същата грешка: Expression<Func<Thing, bool>> validator = t => t.CreatedDate == createdDate && t.Status == Status; и this.myRepository.Received().Query<Thing>(Arg.Is<Expression<Func<Thing, bool>>>(validator)); - person Shevek; 20.10.2014
comment
Всъщност ReSharper след това заявява, че декларациите не са задължителни и .Received става this.myRepository.Received().Query(Arg.Is(validator));, но все пак се проваля на теста - person Shevek; 20.10.2014
comment
Моят пример беше предназначен като пример, можете да промените валидатора да бъде: Func<Expression<Thing, bool>, bool> validator = e => true, което ще позволи на теста да премине и ще провери дали методът е бил извикан, но не с правилния израз... - person Rich O'Kelly; 20.10.2014
comment
Мога да тествам дали методът е извикан с помощта на this.myRepository.Received().Query(Arg.Any<Expression<Func<Thing, bool>>>());, но всъщност искам да тествам дали се използва правилната заявка. - person Shevek; 20.10.2014