Sulieman Mansouri
Sulieman Mansouri

Reputation: 579

Mocking IDbContextFactory with NSubstitute, disposes context

I am using IDbContextFactory in a Blazor server application. I want to mock the services. I can do this using Moq, but when switching to NSubstitute, I run into a problem: only the Save method in the service will pass, while all the rest fail with the following error:

System.ObjectDisposedException: cannot access a disposed context instance. A common cause of this error is disposing a context instance that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling 'Dispose' on the context instance, or wrapping it in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.
Object name: 'LibraryContext'.

Here is my test setup with one of the test cases

public class AuthorServiceTests
{
    private DbContextOptions<LibraryContext> CreateNewContextOptions()
    {
        return new DbContextOptionsBuilder<LibraryContext>()
            .UseInMemoryDatabase(Guid.NewGuid().ToString())
            .Options;
    }

    private IDbContextFactory<LibraryContext> GetDbContextFactory(DbContextOptions<LibraryContext> options)
    {
        var factory = Substitute.For<IDbContextFactory<LibraryContext>>();
        factory.CreateDbContext().Returns(new LibraryContext(options));
        return factory;
    }

    [Fact]
    public async Task Save_ShouldAddAuthor()
    {
        // Arrange
        var options = CreateNewContextOptions();
        var factory = GetDbContextFactory(options);
        var service = new AuthorService(factory);
        var author = new Author { Name = "Author1", Phone = "123456789", Email = "[email protected]" };

        // Act
        await service.Save(author);

        // Assert
        using var context = new LibraryContext(options);
        var savedAuthor = await context.Authors.FirstOrDefaultAsync(a => a.Name == "Author1");
        Assert.NotNull(savedAuthor);
    }

    [Fact]
    public async Task Delete_ShouldRemoveBook()
    {
        // Arrange
        var options = CreateNewContextOptions();
        var factory = GetDbContextFactory(options);
        var service = new BookService(factory);
        var book = new Book {Id = 1, Title = "Book1", ISBN = "1234567890", NumberOfPages = 500, Language = "Arabic" };
        await service.Save(book);

        // Act
        await service.Delete(book);

        // Assert
        using var context = new LibraryContext(options);
        var deletedBook = await context.Books.FindAsync(book.Id);
        Assert.Null(deletedBook);
    }
}

If I change to Moq everything works fine:

private IDbContextFactory<LibraryContext> GetDbContextFactory(DbContextOptions<LibraryContext> options)
{
    var mockFactory = new Mock<IDbContextFactory<LibraryContext>>();
    mockFactory.Setup(f => f.CreateDbContext()).Returns(() => new LibraryContext(options));
    return mockFactory.Object;
}

I think there is an issue of how NSubstitute creates the subs for the method CreateDbContext from IDbContextFactory.

Any ideas on how to replicate the same behavior of Moq using NSubstitute?

Upvotes: 0

Views: 80

Answers (0)

Related Questions