0

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.

1
  • violating Law of Demeter makes you mocking results returned by mocks. Consider talking directly to the repository. That will make mocking and testing extremely easy. Commented Jun 2, 2020 at 20:10

1 Answer 1

1

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);
Sign up to request clarification or add additional context in comments.

4 Comments

Thanks for your answer! After changing a few access modifiers that kept some methods unreachable, the only thing it doesn't like is the line: .Returns(x => new[] { cat }.AsQueryable); which it says "Cannot convert lambda expression to type 'IQueryable<Category>' because it is not a delegate type."
And if I change it to ".Returns(cat.AsQueryable);" the compiler is happy but the test returns the error " Only sources that implement IAsyncEnumerable can be used for Entity Framework asynchronous operations."
Thank you again for your update. It sounds like the in memory approach is the way to go with EF Core and perhaps I was just trying to go the traditional route.
It looks like I have plenty to learn. I'll mark this as the accepted answer since you sent me in a probably more promising direction.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.