如何在这里使用Moq?

时间:2019-04-16 17:25:17

标签: c# unit-testing mocking moq asp.net-core-webapi

我的WEB API项目正在使用Generic存储库,该存储库实现了如下所示的接口:

public interface IGenericEFRepository<TEntity> where TEntity : class
{
    Task<IEnumerable<TEntity>> Get();
    Task<TEntity> Get(int id);
}

public class GenericEFRepository<TEntity> : IGenericEFRepository<TEntity>
    where TEntity : class
{
    private SqlDbContext _db;
    public GenericEFRepository(SqlDbContext db)
    {
        _db = db;
    }

    public async Task<IEnumerable<TEntity>> Get()
    {
        return await Task.FromResult(_db.Set<TEntity>());
    }

    public async Task<TEntity> Get(int id)
    {
        var entity = await Task.FromResult(_db.Set<TEntity>().Find(new object[] { id }));

        if (entity != null && includeRelatedEntities)
        {
            //Some Code
        }
        return entity;
    }
}

现在我要测试此服务。为此,我使用了以下代码:

public class CustomerControllerTest
{
    CustomerController _controller;
    ICustomerProvider _provider;
    ICustomerInquiryMockRepository _repo;

    public CustomerControllerTest()
    {
        _repo = new CustomerInquiryMockRepository();
        _provider = new CustomerProvider(_repo);
        _controller = new CustomerController(_provider);
    }

     [Fact]
    public async Task Get_WhenCalled_ReturnsOkResult()
    {
        // Act
        var okResult = await _controller.Get();

        // Assert
        Assert.IsType<OkObjectResult>(okResult);
    }

    [Fact]
    public async Task GetById_UnknownCustomerIdPassed_ReturnsNotFoundResult()
    {
        // Act
        var notFoundResult = await _controller.Get(4);

        // Assert
        Assert.IsType<NotFoundResult>(notFoundResult);
    }
}

通过使用如下所示的模拟数据(内存中)手动创建伪造的非通用服务(而不是使用我的真实通用接口,而是使用数据库作为数据源的实现),我的测试可以正常工作 >

public interface ICustomerInquiryMockRepository
{
    Task<IEnumerable<CustomerDTO>> GetCustomers();
    Task<CustomerDTO> GetCustomer(int customerId);
}

它的实现:

public class CustomerInquiryMockRepository : ICustomerInquiryMockRepository
{       
    public async Task<IEnumerable<CustomerDTO>> GetCustomers()
    {
        return await Task.FromResult(MockData.Current.Customers);
    }

    public async Task<CustomerDTO> GetCustomer(int CustomerId)
    {
        var Customer = await Task.FromResult(MockData.Current.Customers.FirstOrDefault(p => p.CustomerID.Equals(CustomerId)));

        if (includeTransactions && Customer != null)
        {
            Customer.Transactions = MockData.Current.Transactions.Where(b => b.CustomerId.Equals(CustomerId)).ToList();
        }

        return Customer;
    }
}

MockData.Current.Customers只是一个简单的虚假(内存中的)客户列表。长话短说,以上测试工作正常,但是我感觉自己已经重复了很多次,所以我决定使用Moq库,而不是手动创建假服务。为此,我使用了像这样的Moq:

public class CustomerControllerTest
{
    CustomerController _controller;
    ICustomerProvider _provider;
    //ICustomerInquiryMockRepository _repo;
    Mock<ICustomerInquiryMockRepository> mockUserRepo;


    public CustomerControllerTest()
    {
        mockUserRepo = new Mock<ICustomerInquiryMockRepository>();
        //_repo = new CustomerInquiryMockRepository();
        _provider = new CustomerProvider(mockUserRepo.Object);
        _controller = new CustomerController(_provider);
    }

    [Fact]
    public async Task Get_WhenCalled_ReturnsOkResult()
    {
        mockUserRepo.Setup(m => m.GetCustomers())
            .Returns(Task.FromResult(MockData.Current.Customers.AsEnumerable()));
        // Act
        var okResult = await _controller.Get();

        // Assert
        Assert.IsType<OkObjectResult>(okResult);
    }


    [Fact]
    public async Task GetById_UnknownCustomerIdPassed_ReturnsNotFoundResult()
    {
        //Arrange
        I don't know how can I use Moq here and in the other parts of my tests

        // Act
        var notFoundResult = await _controller.Get(4);

        // Assert
        Assert.IsType<NotFoundResult>(notFoundResult);
    }

现在我的问题是,Mock在模拟GetCustomers方法时工作正常,因为我只是将GetCustomers方法中的代码粘贴到了CustomerInquiryMockRepositoryReturns对象的Mock方法。但是我真的不知道如何在此存储库中将Mock用于其他方法。是否应该替换Return方法中的所有内容?

1 个答案:

答案 0 :(得分:2)

您可以像这样模拟存储库:

reset_index

如果要模拟df.update(df1) df.drop_duplicates().reset_index() Out[2065]: file id book 0 abc.txt 1 Harry Potter Vol 1 1 ert.txt 2 Lord of the Rings - Vol 1 2 ert.txt 2 NaN 3 ert.txt 2 Harry Potter 的特定值,可以执行以下操作:

var mockUserRepo = new Mock<ICustomerInquiryMockRepository>();
mockUserRepo.Setup(x => x.GetCustomers())
            .Returns(Task.FromResult(MockData.Current.Customers.AsEnumerable());
mockUserRepo.Setup(x => x.GetCustomer(It.IsAny<int>()))
            .Returns(res => Task.FromResult(MockData.Current.Customers.ElementAt(res));

我认为这里的关键是根据要模拟对象的方式使用GetCustomermockUserRepo.Setup(x => x.GetCustomer(It.Is<int>(y => y == 4))) .Returns(res => Task.FromResult(/* error value here */)); 。通常,您还希望模拟生产代码中使用的接口,而不是让生产代码依赖名称中带有It.IsIt.IsAny的东西。我建议不要将生产代码依赖于名为Mock的东西,如果这确实是您在做的,而不仅仅是您提供的MCVE的一部分。

测试通常使用模拟在较高级别上测试应用程序的工作流程,因此您通常希望模拟出服务级别,调用控制器并验证是否按预期调用了服务。例如:

Test

从示例中可以看到,您没有在检查任何一种服务的业务逻辑,而只是在验证使用所需数据调用的方法。该测试建立在对称为称为的方法(而不是任何特定的分支逻辑)的验证之上。


另外,与您的问题无关,Moq也具有很酷的语法,可用于简单的模拟设置:

ICustomerInquiryMockRepository

如果需要在存储库上进行其他设置,则可以使用// Production class sample class ProductionController { public ProductionController(IService1 service1, IService2 service2) { } public void ControllerMethod() { var service1Result = service1.Method(); service2.Method(service1Result); } } // Test sample // arrange var expectedResult = new Service1Result(); var service1 = Mock.Of<IService1>(x => x.Method() == expectedResult); var service2 = Mock.Of<IService2>(x => x.Method(It.Is<Service1Result>(y => y == expectedResult))); var controller = new ProductionController(service1, service2); // act controller.ControllerMethod(); // assert Mock.Get(service1).Verify(x => x.Method(), Times.Once); Mock.Get(service2).Verify(x => x.Method(expectedResult), Times.Once); 。绝对值得一试,我觉得它读起来要好得多。