Search code examples
c#genericsnsubstitute

NSubstitute and AsQuerable not returning results


I have a strange issue. I am creating a generic class and I have a generic method and test. The test looks like this:

[Test]
public async Task ReturnGeneric()
{
    // Assemble
    const int id = 1;
    var request = new GetGeneric<Venue>(id);
    var services = GetGenericContext.GivenServices();
    var handler = services.WhenCreateHandler();
    var venues = Builder<Venue>.CreateListOfSize(20).Build().ToDbSet();

    services.DatabaseContext.Set<Venue>().Returns(venues);

    // Act
    var response = await handler.Handle(request, CancellationToken.None);

    // Assert
    response.Success.Should().BeTrue();
    response.Error.Should().BeNull();
    response.Result.Should().BeOfType<Venue>();
}

}

And the method looks like this:

public async Task<Attempt<T>> Handle(GetGeneric<T, TKey> request, CancellationToken cancellationToken)
{
    var id = request.Id;
    if (EqualityComparer<TKey>.Default.Equals(id, default)) return ValidationError.Required(nameof(id));

    var generics = _databaseContext.Set<T>().AsQueryable();
    var t = _databaseContext.Set<T>().ToList();
    var generic = generics.SingleOrDefault(m => m.Id.Equals(request.Id));
    var x = t.SingleOrDefault(m => m.Id.Equals(id));

    if (generic == null) return NotFoundError.ItemNotFound(nameof(T), request.Id.ToString());

    return generic;
}

variables t and x are just tests for my own sanity. The issue here is that generic is null in my test, but x is not. It seems to have a problem with the AsQueryable() method. For some reason if I do a call to AsQueryable() there are no results in the collection, but there are if it invoke ToList().

This is my extension method for ToDbSet():

public static class DbSetExtensions
{
    public static DbSet<T> ToDbSet<T>(this IEnumerable<T> data) where T : class
    {
        var queryData = data.AsQueryable();
        var dbSet = Substitute.For<DbSet<T>, IQueryable<T>>();

        ((IQueryable<T>)dbSet).Provider.Returns(queryData.Provider);
        ((IQueryable<T>)dbSet).Expression.Returns(queryData.Expression);
        ((IQueryable<T>)dbSet).ElementType.Returns(queryData.ElementType);
        ((IQueryable<T>)dbSet).GetEnumerator().Returns(queryData.GetEnumerator());

        return dbSet;
    }
}

Can anyone think of a reason why this is not working?


The entire class looks like this:

public class GenericGet<T, TKey> : IRequest<Attempt<T>> where T: TClass<TKey>
{
    public TKey Id { get; }

    public GenericGet(TKey id)
    {
        Id = id;
    }
}

public class GenericGet<T> : GenericGet<T, int> where T : TClass<int>
{
    public GenericGet(int id) : base(id)
    {
    }
}

public class GenericGetHandler<T, TKey> : IRequestHandler<GenericGet<T, TKey>, Attempt<T>> where T: TClass<TKey>
{
    private readonly DatabaseContext _databaseContext;

    public GenericGetHandler(DatabaseContext databaseContext)
    {
        _databaseContext = databaseContext;
    }

    public async Task<Attempt<T>> Handle(GenericGet<T, TKey> request, CancellationToken cancellationToken)
    {
        var id = request.Id;
        if (EqualityComparer<TKey>.Default.Equals(id, default)) return ValidationError.Required(nameof(id));

        var generics = _databaseContext.Set<T>().AsQueryable();
        var generic = generics.SingleOrDefault(m => m.Id.Equals(request.Id));

        if (generic == null) return NotFoundError.ItemNotFound(nameof(T), request.Id.ToString());

        return generic;
    }
}

public class GenericGetHandler<T> : GenericGetHandler<T, int> where T : TClass<int>
{
    public GenericGetHandler(DatabaseContext databaseContext) : base(databaseContext)
    {
    }
}

And a venue looks like this:

public class Venue: TClass<int>
{
    [Required, MaxLength(100)] public string Name { get; set; }
    [MaxLength(255)] public string Description { get; set; }

    public IList<Theatre> Theatres { get; set; }
}

Solution

  • I changed my DatabaseContext mock to actually use the in memory provider as suggested by Fabio. It looks like this:

    public class DatabaseContextContext
    {
        public DatabaseContext DatabaseContext;
        protected DatabaseContextContext()
        {
            var options = new DbContextOptionsBuilder<DatabaseContext>()
                .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
                .EnableSensitiveDataLogging()
                .Options;
    
            DatabaseContext = new DatabaseContext(options);
        }
    }
    

    And then my GetGenericContext looks like this:

    public class GenericGetContext<T> : DatabaseContextContext where T : TClass<int>
    {
        public static GenericGetContext<T> GivenServices() => new GenericGetContext<T>();
    
        public GenericGetHandler<T> WhenCreateHandler() => new GenericGetHandler<T>(DatabaseContext);
    }
    

    Instead of doing:

    services.DatabaseContext.Set<Venue>().Returns(venues);
    

    We are not mocking anymore, so we can actually do:

    services.DatabaseContext.Venues.AddRange(venues);
    services.DatabaseContext.SaveChanges();
    

    We must call save changes otherwise it won't actually say the venues to the collection. Once you do that, everything works as expected.