nsubstitute/NSubstitute

I can't test code that has dbcontext.SomeEntity.SingleOrDefault()

TechyGuy99 opened this issue · 2 comments

I get the following error when when i run the test below.

The code i am testing uses SingleOrDefault().  How do i test code that users that function?  

X.WctAPI.Tests.CommentResolutionRolesTest.SingleDefaultTest
   Source: CommentResolutionRolesTest.cs line 89
   Duration: 132 ms

  Message: 
System.InvalidOperationException : The provider for the source 'IQueryable' doesn't implement 'IAsyncQueryProvider'. Only providers that implement 'IAsyncQueryProvider' can be used for Entity Framework asynchronous operations.

  Stack Trace: 
EntityFrameworkQueryableExtensions.ExecuteAsync[TSource,TResult](MethodInfo operatorMethodInfo, IQueryable`1 source, Expression expression, CancellationToken cancellationToken)
EntityFrameworkQueryableExtensions.ExecuteAsync[TSource,TResult](MethodInfo operatorMethodInfo, IQueryable`1 source, LambdaExpression expression, CancellationToken cancellationToken)
EntityFrameworkQueryableExtensions.SingleOrDefaultAsync[TSource](IQueryable`1 source, Expression`1 predicate, CancellationToken cancellationToken)
CommentResolutionRolesTest.SingleDefaultTest() line 117
--- End of stack trace from previous location ---

`   [Fact]
   public async Task SingleDefaultTest()
   {
       // fake dbset here

       var crSet = Substitute.For<DbSet<CommentResolutionRole>, IQueryable<CommentResolutionRole>, IDbAsyncEnumerable<CommentResolutionRole>>();

       var roles = new List<CommentResolutionRole>();
       roles.Add(new CommentResolutionRole() { Id = 1, CommentId = 1, UserId = "A" });
       roles.Add(new CommentResolutionRole() { Id = 2, CommentId = 1, UserId = "B" });
       roles.Add(new CommentResolutionRole() { Id = 3, CommentId = 1, UserId = "C" });

       var comments = new Comment[]
       {
           new Comment() { Id=1, ResolutionRoles = new List<CommentResolutionRole>() }
       };

       
       var commentSet = Substitute.For<DbSet<Comment>, IQueryable<Comment>, IDbAsyncEnumerable<Comment>>();

       commentSet.AddRange(comments.AsQueryable());


       var context = Substitute.For<DataDbContext>(new DbContextOptions<DataDbContext>());


       context.CommentResolutionRole.Returns(crSet);
       context.Comments.Returns(commentSet);
       context.Comments.FindAsync(1).Returns(new Comment { Id = 1 });
       context.Comments.SingleOrDefaultAsync(c => c.Id == 1).Returns(new Comment() { Id = 1 });

       Assert.True(comments.SingleOrDefault(c => c.Id == 1) != null);



   }`

There are a lot of problems with this code. NSubstitute is mainly designed to mock interfaces. So it is best to hide your DbContext behind access interfaces.

You can use NSubstitute for classes, but then you must really know what you are doing. You can only mock virtual methods/properties and you cannot use any extension methods.

To make a long story short, you are much better with writing tests against the DbContext with the in memory database.

There are a lot of problems with this code. NSubstitute is mainly designed to mock interfaces. So it is best to hide your DbContext behind access interfaces.

You can use NSubstitute for classes, but then you must really know what you are doing. You can only mock virtual methods/properties and you cannot use any extension methods.

To make a long story short, you are much better with writing tests against the DbContext with the in memory database.

I will look at just using a real db. I will create and destroy it with each test using the dbcontext. Might be slow, but should work.