2

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?

Shevek
  • 3,869
  • 5
  • 43
  • 63

2 Answers2

1

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.

Community
  • 1
  • 1
mkoertgen
  • 898
  • 6
  • 14
0

The default equality comparer for an Expression is being used (referential equality):

eg, the expression (t => t.CreatedDate == createdDate && t.Status == Status``) in:

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

Is a different instance to the expression in:

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

To fix validate this method call check out argument matchers within NSubstitute.

But as an example:

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));
Noctis
  • 11,507
  • 3
  • 43
  • 82
Rich O'Kelly
  • 41,274
  • 9
  • 83
  • 114
  • I'm not sure how to go about implementing that. I have tried `this.myRepository.Received().Query(t => t.CreatedDate == Arg.Any() && t.Status == Arg.Any());` and `this.myRepository.Received().Query(t => t.CreatedDate == Arg.Is(createdDate) && t.Status == Arg.Is(Status));` but both are still using `value()` and failing – Shevek Oct 20 '14 at 13:59
  • I couldn't get your example to compile. This compiles but the test fails with the exact same error: `Expression> validator = t => t.CreatedDate == createdDate && t.Status == Status;` and `this.myRepository.Received().Query(Arg.Is>>(validator));` – Shevek Oct 20 '14 at 15:24
  • In fact, ReSharper then states that the declarations aren't required and the .Received becomes `this.myRepository.Received().Query(Arg.Is(validator));` but still fails the test – Shevek Oct 20 '14 at 15:26
  • My example was meant as an example, you can change validator to be: `Func, bool> validator = e => true` which will allow the test to pass, and will verify that the method was called, but not with the correct expression... – Rich O'Kelly Oct 20 '14 at 15:30
  • I can test that the method was called using `this.myRepository.Received().Query(Arg.Any>>());` but I do actually want to test that the correct query is being used. – Shevek Oct 20 '14 at 15:32