0
votes

I have the following xunit test using Moq

[Fact]
public async void Add_Valid()
{
    // Arrange
    var mockSet = new Mock<DbSet<CategoryDao>>();

    var mockContext = new Mock<Data.Context.AppContext>();
    mockContext.Setup(m => m.Categories).Returns(mockSet.Object);

    var categoryProfile = new CategoryVoProfile();
    var configMapper = new MapperConfiguration(cfg => cfg.AddProfile(categoryProfile));
    IMapper mapper = new Mapper(configMapper);

    var service = new InDbCategoryService(mockContext.Object, mapper);

    // Act
    await service.Add(new CategoryVo() { Name = "CreatedName1" });

    // Assert
    mockSet.Verify(m => m.Add(It.IsAny<CategoryDao>()), Times.Once()); // DbSet verification
    mockContext.Verify(m => m.SaveChanges(), Times.Once());            // DbContext verification
}

And it throws this error:

Moq.MockException:
Expected invocation on the mock once, but was 0 times: m => m.Add(It.IsAny())

Performed invocations:
Mock<DbSet:1> (m): No invocations performed.

When I delete the DbSet verification line and ask to verify only the DbContext, it throws this:

Moq.MockException :
Expected invocation on the mock once, but was 0 times: m => m.SaveChanges()

Performed invocations:
MockAppContext:1 (m):
AppContext.Categories = InternalDbSet
DbContext.Add(CategoryDao)
DbContext.SaveChangesAsync(CancellationToken)


The simplified service looks like this:

public class InDbCategoryService : IDataServiceAsync<CategoryVo>
{
    private readonly Data.Context.AppContext context;
    private readonly IMapper mapper;
    public InDbCategoryService(Data.Context.AppContext context, IMapper mapper)
    {
        this.context = context;
        this.mapper = mapper;
    }

    public async Task Add(CategoryVo item)
    {
        context.Add(entity: mapper.Map<CategoryDao>(item));
        await context.SaveChangesAsync();
    }
}

The category profile:

public class CategoryVoProfile : Profile
{
    public CategoryVoProfile()
    {
        CreateMap<CategoryDao, CategoryVo>()
            .ReverseMap();
    }
}

Database context:

public class AppContext : DbContext
{
    public AppContext() { }

    public AppContext (DbContextOptions<AppContext> options) : base(options) { }

    public virtual DbSet<CategoryDao> Categories { get; set; }

}

I've used this microsoft docs example for my test, but it's clear I'm missing something. Any help or advice is appreciated.

1
Alternative way not to mock DbContext, but use in memory provider or test it's actions against actual database. Mocking DbContext provide little value, because it just testing sequence of called methods.Fabio

1 Answers

1
votes

You are not testing the methods that you've called in your service. Your add method:

public async Task Add(CategoryVo item)
{
    context.Add(entity: mapper.Map<CategoryDao>(item));
    await context.SaveChangesAsync();
}

You'll note you're calling the DbContext.Add not the context.Categories.Add which is what you verify in your test:

mockSet.Verify(m => m.Add(It.IsAny<CategoryDao>()), Times.Once());

The same is true for your SaveChanges. You're verifying the synchronous version but calling the async one. So you need to modify what you're verifying to match what you're using.