如何模拟 IRepository<T>?

How to mock IRepository<T>?

我想模拟一个内部存储库的工作单元接口,用于单元测试。到目前为止,我可以像下面那样做。

namespace Liquid.Service.UnitTest
{
    using Liquid.DataAccess.Interface;
    using Liquid.Domain;
    using Domain.Interface;
    using Moq;
    using System.Collections.Generic;
    using System.Linq;

    internal class Helper
    {
        internal Mock<IUnitOfWork> MockUnitOfWork(ICollection<Dummy> dummies = null,
            ICollection<ProductType> productTypes = null)
        {
            dummies = dummies ?? new List<Dummy>();
            productTypes = productTypes ?? new List<ProductType>();

            var dummyRepositoryMock = MockDummyRepository(dummies);
            var productTypeRepositoryMock = MockProductTypeRepository(productTypes);

            var unitOfWorkMock = new Mock<IUnitOfWork>();
            unitOfWorkMock.Setup(x => x.DummyRepository)
                .Returns(dummyRepositoryMock.Object);
            unitOfWorkMock.Setup(x => x.ProductTypeRepository)
                .Returns(productTypeRepositoryMock.Object);

            return unitOfWorkMock;
        }

        private Mock<IDummyRepository> MockDummyRepository(ICollection<Dummy> dummies)
        {
            var dummyRepositoryMock = new Mock<IDummyRepository>();

            dummyRepositoryMock.Setup(x => x.FindById(It.IsAny<int>()))
                .Returns((int arg1) => dummies.Where(x => x.Id == arg1).SingleOrDefault());

            dummyRepositoryMock.Setup(x => x.Add(It.IsAny<Dummy>()))
                .Callback((Dummy arg1) => dummies.Add(arg1));

            return dummyRepositoryMock;
        }

        private Mock<IProductTypeRepository> MockProductTypeRepository(ICollection<ProductType> productTypes)
        {
            var productTypeRepositoryMock = new Mock<IProductTypeRepository>();

            productTypeRepositoryMock.Setup(x => x.FindById(It.IsAny<int>()))
                .Returns((int arg1) => productTypes.SingleOrDefault(x => x.Id == arg1));

            productTypeRepositoryMock.Setup(x => x.Add(It.IsAny<ProductType>()))
                .Callback((ProductType arg1) => productTypes.Add(arg1));

            return productTypeRepositoryMock;
        }
    }
}

你看到我已经创建了两个方法来模拟 DummyRepository 和 ProductTypeRepository 但因为它有相同的实现,我认为它对于我拥有的每个存储库都是多余的。 下面是存储库和 IRepository 代码。

namespace Liquid.DataAccess.Interface
{
    using Liquid.Domain;
    using System.Collections.Generic;
    using System.Threading;
    using System.Threading.Tasks;

    public interface IDummyRepository : IRepository<Dummy>
    {
    }

    public interface IProductTypeRepository : IRepository<ProductType>
    {
    }

    public interface IRepository<TEntity> where TEntity : class
    {
        IList<TEntity> GetAll();

        Task<List<TEntity>> GetAllAsync();

        Task<List<TEntity>> GetAllAsync(CancellationToken cancellationToken);

        IList<TEntity> PageAll(int skip, int take);

        Task<List<TEntity>> PageAllAsync(int skip, int take);

        Task<List<TEntity>> PageAllAsync(CancellationToken cancellationToken, int skip, int take);

        TEntity FindById(object id);

        Task<TEntity> FindByIdAsync(object id);

        Task<TEntity> FindByIdAsync(CancellationToken cancellationToken, object id);

        void Add(TEntity entity);

        void Update(TEntity entity);

        void Remove(TEntity entity);
    }
}

如何使用相同的方法模拟每个继承 IRepository 的存储库实现?

更新: 测试只是一个简单的添加和检查,如下所示。

    [Test]
    public void ProductTypeService_Add()
    {
        // GIVEN
        var productTypeData = new ProductType()
        {
            Id = 1,
            Description = "ProductType1"
        };

        // WHEN
        var unitOfWorkMock = new Helper().MockUnitOfWork();
        var productTypeService = new ProductTypeService(unitOfWorkMock.Object);
        productTypeService.Add(productTypeData);
        unitOfWorkMock.Verify(x => x.SaveChanges());

        // THEN
        Assert.That(productTypeService.FindById(1) != null);
        Assert.That(productTypeService.FindById(2) == null);

        // WHEN
        var productTypeData2 = new ProductType()
        {
            Id = 2,
            Description = "ProductType2"
        };

        productTypeService.Add(productTypeData2);

        // THEN
        Assert.That(productTypeService.FindById(2) != null);
    }

恕我直言,您正在测试错误的东西;也就是说,您正在测试内存中的集合(List<T>)是否可以存储数据,并且可以在集合中找到数据。 这总是会产生 true,因为这是内存中集合的目的

您需要创建 集成测试 而不是这样做,它将使用底层存储库的实际实现(例如 Entity Framework)或只是 像这样测试您的服务的行为

[Test]
public void ProductTypeService_WhenAddingNewItem_CallsSaveChanges()
{
    var unitOfWork = new Mock<IUnitOfWork>();
    // setup the properties of the mock here...

    var service = new ProductTypeService(unitOfWork);
    service.Add(new ProductType { Id = 2, Description = "some product" });

    unitOfWork.AssertWasCalled(_ => _.SaveChanges());
}

这样,您测试您的服务是否调用了 SaveChanges() 方法;实际上保存数据是存储库的责任,正如我上面所说,测试列表是否可以将数据存储在内存中是没有用的。

我认为您的问题过于复杂,因此您的解决方案也过于复杂。如果您不向它添加任何值,则不需要为各种存储库提供接口,例如 IDummyRepositoryIPproductRepository

您的数据类

public class Dummy
{
    public int Id { get; set; }
}

public class ProductType
{
    public int Id { get; set; }
}

你的 ProductTypeService(我只能这样假设)

public class ProductTypeService
{
    private readonly IUnitOfWork _unitOfWork;

    public ProductTypeService(IUnitOfWork unitOfWork)
    {
        _unitOfWork = unitOfWork;
    }

    public void AddProductType(ProductType productType)
    {
        _unitOfWork.ProductTypes.Add(productType);
    }
}

你的 IUnitOfWork

public interface IUnitOfWork
{
    IRepository<Dummy> Dummies { get; set; }
    IRepository<ProductType> ProductTypes { get; set; }
}

您的 IRepository 保持不变,所以我不会复制粘贴到这里!

最后你的单元测试

[TestFixture]
public class Class1
{
    private Mock<IUnitOfWork> _unitOfWorkMock;
    private Mock<IRepository<Dummy>> _dummyRepositoryMock;
    private Mock<IRepository<ProductType>> _productTypeRepositoryMock;

    [SetUp]
    public void Setup()
    {
        _unitOfWorkMock = new Mock<IUnitOfWork>();
        _dummyRepositoryMock = CreateMock<Dummy>();
        _productTypeRepositoryMock = CreateMock<ProductType>();

        _unitOfWorkMock.Setup(u => u.Dummies).Returns(_dummyRepositoryMock.Object);
        _unitOfWorkMock.Setup(u => u.ProductTypes).Returns(_productTypeRepositoryMock.Object);
    }

    [Test]
    public void product_type_service_should_add_item_to_the_underlying_repository()
    {
        var productTypeService = new ProductTypeService(_unitOfWorkMock.Object);
        var productType = new ProductType {Id = 10};
        productTypeService.AddProductType(productType);
        _productTypeRepositoryMock.Verify(r => r.Add(It.Is<ProductType>(p => p.Id == productType.Id)), Times.Once());
    }

    private Mock<IRepository<T>> CreateMock<T>() where T : class
    {
        var mock = new Mock<IRepository<T>>();

        // do more common magic here

        return mock;
    }
}