NSubstitute与Linq表达式不匹配[英] NSubstitute not matching Linq Expression

本文是小编为大家收集整理的关于NSubstitute与Linq表达式不匹配的处理/解决方法,可以参考本文帮助大家快速定位并解决问题,中文翻译不准确的可切换到English标签页查看源文。

问题描述

我正在实现一个存储库模式查询类并使用 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(),这在 Application 和 Test 之间是不同的.

这是为什么?我该如何解决这个问题?

推荐答案

对于复杂的表达式,如果经常发现使用 回调 比使用 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();

这种方法似乎使测试更加黑盒化,但这通常不是一件坏事.

本文地址:https://www.itbaoku.cn/post/1556790.html

问题描述

I am implementing a repository pattern Query class and testing using NSubstitute.

Repository interface:

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

DateTimeProvider interface:

public interface IMyDateTimeProvider
{
    DateTime GetDateNow();
}

Application interface:

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

Application implementation:

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();
    }
}

Test:

[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);
    }
}

But the test fails with the following message:

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))*)

The DateTime and Status are being parsed into value() which are different between the Application and the Test.

Why is this? How can I fix this?

推荐答案

For complicate expressions if often find it easier to assert on captured arguments by using callbacks than with Received(). An (incomplete) example:

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

Then, assert on the captured filter expression. It might actually simpler to just execute the expression's filter func (see e.g. here)

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();

This approach seems to make the test a little more black-boxed but this is in general not a bad thing.