【问题标题】:Unit testing mocked repositories in a C# Unit of Work project?在 C# 工作单元项目中对模拟存储库进行单元测试?
【发布时间】:2023-02-24 02:37:51
【问题描述】:

我们的项目是一个使用 CLEAN 模式的 Swashbuckle.AspNetCore 项目设置。 基础设施层采用工作单元模式构建。

我正在尝试使用 MSTest 和 Moq4 创建单元测试存储库。现在我的 问题是我如何正确地对服务进行单元测试?依赖注入是 对我来说太复杂了。我不明白的是如何在 具有模拟的 UnitOfWork 对象的单元测试函数 应用程序数据库上下文。

据我所知,GenericRepository、context 和 UnitOfWork 并没有紧密耦合 (如 https://stackoverflow.com/questions/21847306/how-to-mock-repository-unit-of-work 中所推荐)。

我正在使用的代码如下所示:

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();
    }
}

我未能正确创建模拟的 UnitOfWork 对象,我不知道如何重现单元测试中自动发生的依赖注入。

【问题讨论】:

    标签: c# mstest


    【解决方案1】:

    单元测试应该与其所有外部依赖项分开,否则您将进入集成测试领域。由于您正在尝试对您的服务进行单元测试,因此您只需要模拟那些接口——这意味着您根本不需要模拟 ApplicationDBContext。这就是分离之美。

    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
    service.DoWork();
    
    // Assert your repository calls
    mockRepository.Verify(...)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-12-27
      • 2019-08-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多