0

Our project is a Swashbuckle.AspNetCore project setup using the CLEAN pattern. The infrastructure layer is structured with the Unit of Work pattern.

I'm trying to create a unit test repository using MSTest and Moq4. Now my question is how do I Unit test the Service properly? The dependency injection is too complicated for me. What I dont understand is how to instantiate a Service object in the unit test function with a UnitOfWork object that has a mocked ApplicationDbContext.

As far as I can tell the GenericRepository, context and UnitOfWork are not tightly coupled (as recommended in 21847306/how-to-mock-repository-unit-of-work).

The code I'm working with looks like the following:

public interface IGenericRepository<T> where T : class
{
    IQueryable<T> All();
    void Delete(T entity);
    //.. Other functions
}

public class GenericRepository<T> : IGenericRepository<T> where T : class
{
    protected readonly ApplicationDBContext _context;
    protected readonly IConfiguration _configuration;
    protected DbSet<T> dbSet;

    public GenericRepository(ApplicationDBContext context, IConfiguration configuration)
    {
        _context = context;
        _configuration = configuration;
        this.dbSet = _context.Set<T>();
    }

    public IQueryable<T> All()
    {
        return _context.Set<T>().AsQueryable().AsNoTracking();
    }

    public void Delete(T entity)
    {
        _context.Set<T>().Remove(entity);
    }

    //.. Other functions
}

public interface ISomeRepository : IGenericRepository<Some>
{
    public Task<bool> AddSomeWithCustomLogicAsync(Some some);
    public Task<bool> DeleteSomeWithCustomLogicAsync(int someId);
}

class SomeRepository : GenericRepository<Some>, ISomeRepository
{
    public SomeRepository(ApplicationDBContext dbContext, IConfiguration configuration) : base(dbContext, configuration)
    {
    }

    public async Task<bool> AddSomeWithCustomLogicAsync(Some some)
    {
        // Add logic..
    }

    public async Task<bool> DeleteSomeWithCustomLogicAsync(int someId)
    {
        // Delete logic..
    }
}
public interface IUnitOfWork : IDisposable
{
    public ISomeRepository SomeRepository { get; }
    public IAnotherRepository AnotherRepository { get; }

    int SaveChanges();
}

public class UnitOfWork : IUnitOfWork
{
    private readonly ApplicationDBContext _dBContext;
    private readonly IConfiguration _configuration;
    private readonly ILogger _logger;

    private ISomeRepository _someRepository;
    private IAnotherRepository _anotherRepositoty;

    public UnitOfWork(ApplicationDBContext applicationDBContext, ILoggerFactory loggerFactory, IConfiguration configuration)
    {
        _dBContext = applicationDBContext;
        _configuration = configuration;
        _logger = loggerFactory.CreateLogger("logs");
    }

    public ISomeRepository SomeRepository
    {
        get
        {
            _someRepository ??= new SomeRepository(_dBContext, _configuration);
            return _someRepository;
        }
    }

    public int SaveChanges()
    {
        return _dBContext.SaveChanges();
    }
}
public abstract class GenericService<T> : IGenericService<T> where T : class
{
    public IUnitOfWork _unitOfWork;

    protected readonly IMapper _mapper;
    protected readonly IValidateService _validateService;
    protected readonly ISignalService _signalService;
    protected readonly ILogBoekService _logBoekService;
    protected readonly IHttpContextAccessor _context;
    private readonly IUriService _uriService;

    public GenericService(IUnitOfWork unitOfWork, IMapper mapper, IValidateService validateService, ISignalService signalService,
                          ILogBoekService logBoekService, IHttpContextAccessor context, IUriService uriService)
    {
        _unitOfWork = unitOfWork;
        _mapper = mapper;
        _validateService = validateService;
        _signalService = signalService;
        _logBoekService = logBoekService;
        _context = context;
        _uriService = uriService;
    }
}

public interface ISomeService : IGenericService<Some>
{
    public Task<bool> DoWork();
}

public class SomeService : GenericService<Some>, ISomeService
{
    public SomeService(IUnitOfWork unitOfWork, IMapper mapper, IValidateService validateService,
                       ISignalService signalService, ILogBoekService logBoekService, IHttpContextAccessor context,
                       IUriService uriService)
        : base(unitOfWork, mapper, validateService, signalService, logBoekService, context, uriService)
    {
    }

    // This is the function I want to test
    public async Task<bool> DoWork()
    {
        return await _unitOfWork.SomeRepository.All() == 0;
    }
}
[TestClass]
public class SomeUnitTest
{
    private SomeService _someService;

    public void GenerateService(IQueryable<Some> documenten)
    {
        var mockDbSet = new Mock<DbSet<Some>>();
        mockDbSet.As<IQueryable<Some>>().Setup(x => x.Provider).Returns(documenten.Provider);
        mockDbSet.As<IQueryable<Some>>().Setup(x => x.Expression).Returns(documenten.Expression);
        mockDbSet.As<IQueryable<Some>>().Setup(x => x.ElementType).Returns(documenten.ElementType);
        mockDbSet.As<IQueryable<Some>>().Setup(x => x.GetEnumerator()).Returns(documenten.GetEnumerator());

        var mockContext = new Mock<ApplicationDBContext>();
        mockContext.Setup(x => x.Some).Returns(mockDbSet.Object);

        // This seems like an inefficient way of doing it, how can it be improved?
        var unitOfWork = new UnitOfWork(mockContext.Object, null, null);
        _someService = new SomeService(unitOfWork, null, null, null, null, null, null);
    }

    [TestMethod]
    public async Task GetPaginatedSomeAsyncTest()
    {
        // Prepare data

        var someThings = new List<Some> {
            new Some { Id = 1, Name = "Some 1" },
            new Some { Id = 2, Name = "Some 2" },
            new Some { Id = 3, Name = "Some 3" }
        }.AsQueryable();

        GenerateService(someThings);

        // Test

        var retrievedDocumenten = await _someService.DoWork();

        Assert.AreEqual(0, retrievedDocumenten.Data.Count);

        return;
    }

    [TestMethod]
    public void GetSomeAsyncTest()
    {
        Assert.Fail();
    }
}

I did not succeed at creating a mocked UnitOfWork object properly, I dont know how to reproduce the dependency injection that occurs automatically in the unit test.

Riyyi
  • 98
  • 5

1 Answers1

1

A Unit Test should be separated from all of it's external dependencies, otherwise you enter integration test territory. Since you are trying to unit test your Service, you only need to mock those interfaces - meaning you don't need to mock the ApplicationDBContext at all. This is the beauty of separation.

var mockRepository = new Mock<ISomeRepository>();
var mockUnitOfWork = new Mock<IUnitOfWork>();
var service = new SomeService(mockUnitOfWork.Object, Mock.Of<IMapper>(), Mock.Of<IValidateService>(), Mock.Of<ISignalService>(), Mock.Of<ILogBoekService>(), Mock.Of<IHttpContextAccessor>(), Mock.Of<IUriService>());

// Setup the mock UOW to return the mock repository
mockUnitOfWork
    .Setup(uow => uow.SomeRepository)
    .Returns(mockRepository.Object)

// Mock any repository calls here
mockRepository.Setup(...).Returns(...)

// Call your service
var retrievedDocumenten = service.DoWork();

// Assert your result
Assert.AreEqual(0, retrievedDocumenten.Data.Count);

// Optionally assert repository calls
mockRepository.Verify(...)
itsdaniel0
  • 1,059
  • 3
  • 11
  • 28
  • Isn't an integration test also done via a separate project using MSTest? Almost everything we do is data dependent. – Riyyi Feb 24 '23 at 13:51
  • @Riyyi It purely depends on your project structure. They can be (and often are) their own project, but there's nothing enforcing that – itsdaniel0 Feb 24 '23 at 16:45