Search code examples
c#unit-testingmoqxunitunit-of-work

Unit Testing Service with Unit of Work with xunit .NET Core 3.1 and Moq


being fairly new to xUnit, I have an application that uses the Unit of Work pattern to return data to a service and I'm trying to unit test this service.

Here is the service code:

 public async Task<IEnumerable<CatDto>> GetActiveCatsAsync()
    {
        var catList = new List<CatDto>();
        var catEFList = await _uow.Repository<Cat>().GetActiveCatsAsync();
        catList.AddRange(catEFList.Select(c => new CatDto
        {
            CatId = c.CatId,
            CatName = c.CatName,
            DisplayOrd = c.DisplayOrd
        }));


        return catList;
    }

And here is the extension that this service calls:

 public static async Task<List<Cat>> GetActiveCatsAsync(this IRepository<Cat> repository)
    {
        return await repository.AsQueryableRepo().Items.Where(c =>c.IsActive == true).OrderBy(c=>c.DisplayOrd).ToListAsync();
    }
}

Here is the Interface used by the extension:

 internal static IQueryableRepository<T> AsQueryableRepo<T>(this IRepository<T> repository) where T : class
    {
        return (IQueryableRepository<T>)repository;
    }

Here is the concrete unit of work class:

 public class SQLUnitOfWork<TContext> : BaseUnitOfWork, ISQLUnitOfWork<TContext>
    where TContext : IDbContext, IDisposable
{
    public SQLUnitOfWork(TContext context) : base(context)
    {
    }
}

And here is where I am with a single unit test so far:

  [Fact]
    public void GetActiveCatsAsync_ReturnsTypeIEnumerableCatDto()
    {
        var mockLogger = new Mock<ILogger<CatService>>();
        var mockUoW = new Mock<ISQLUnitOfWork>();

        var catServ = new CatService(mockLogger.Object, mockUoW.Object);

        var result = catServ.GetActiveCatsAsync();

        Assert.IsType<Task<IEnumerable<CatDto>>>(result);
    }

This test actually passes, but obviously the "result" variable has no data in it because there is no setup for the mocks. I also tried setting up:

 var mockRepo = new Mock<IRepository<Cat>>();

But this doesn't allow me to get at the extensions methods above. It doesn't show me the "asQueryableRepo()" method to provide a return value for. So I'm not sure how I can mock the return data, which would be a simple Cat object with three properties shown in the service call. I have also tried using xUnit's .ReturnAsync() and Task.FromResult() functionality.

After a few searches, I tried implementing xUnit.DependencyInjection, but that requires configuration in the test project's Startup.cs file. My test project (created with the Visual Studio xUnit project template) doesn't have a Startup.cs file. Did I use the wrong template?

I have also seen something similar to the following used:

      scope = new CustomWebApplicationFactory<Startup>().Server.Host.Services.CreateScope();
service = scope.ServiceProvider.GetRequiredService<IDashboardService>();
context = scope.ServiceProvider.GetRequiredService<DTBContext>();

Which also seems to reference a startup class, so I really haven't been able to get this approach working either. Do I need to start again with a project that includes a Startup.cs folder? Or can I Unit Test the code above without it? I'm still researching this, but so far I cannot find a way to mock the dependencies of the service calls and the Unit of Work elements.

Any suggestions or advice is greatly appreciated! Thank you!

UPDATE: I think I now understand the xUnit.DependencyInjection, so I added a startup.cs class, and added the references referred to on their GitHub page. I also had to install Microsoft.Extensions.Hosting package to take care of a "does not have an implementation" error and now my project has a startup and the test that passed above now passes. I'm hoping that I can now handle dependencies within the testing project, but I'll find out.


Solution

  • You can try using As:

    [Fact]
    public async Task GetActiveCatsAsync_ReturnsTypeIEnumerableCatDto()
    {
        var mockLogger = new Mock<ILogger<CatService>>();
        var mockRepo = new Mock<IRepository<Cat>>();
        var cat = new Cat
        {
            IsActive = true
        };
        mockRepo
            .As<IQueryableRepository<Cat>>()
            .Setup(x => x.Items)
            .Returns(x => new[] { cat }.AsQueryable);
        var mockUoW = new Mock<ISQLUnitOfWork>();
        mockUoW.Setup(x => x.Repository<Cat>()).Returns(mockRepo.Object);
        var catServ = new CatService(mockLogger.Object, mockUoW.Object);
    
        var result = await catServ.GetActiveCatsAsync();
    
        var single = Assert.Single(result);
        Assert.Equal(cat, single);
    }
    

    UPDATE:

    Yes, testing Entity Framework is a separate issue. In that case, I would recommend testing the repository separately using InMemoroyDatabase or something, which would be inappropriate to pull in to the service test.

    To really just test the service is doing what it's supposed to do, ideally you would verify like this:

    [Fact]
    public async Task GetActiveCatsAsync_ReturnsTypeIEnumerableCatDto()
    {
        var mockLogger = new Mock<ILogger<CatService>>();
        var mockRepo = new Mock<IRepository<Cat>>();
        var mockUoW = new Mock<ISQLUnitOfWork>();
        mockUoW.Setup(x => x.Repository<Cat>()).Returns(mockRepo.Object);
        var catServ = new CatService(mockLogger.Object, mockUoW.Object);
    
        var result = await catServ.GetActiveCatsAsync();
    
        mockRepo.Verify(x => x.GetActiveCatsAsync(), Times.Once);
    }
    

    But you won't be able to do this on an extension method. Unfortunately, the code you're testing doesn't lend itself to easily writing pure unit tests against it. Specifically, having the UoW expose the repository is troublesome. It would be easier to have ISQLUnitOfWork have its own GetActiveCatsAsync method so the repository wouldn't be visible (leaky abstraction) - that way you could do the verification directly on the UoW:

    mockUoW.Verify(x => x.GetActiveCatsAsync(), Times.Once);