【问题标题】:How to mock a service with FakeItEasy?如何使用 FakeItEasy 模拟服务?
【发布时间】:2020-08-10 10:47:11
【问题描述】:

我有一个通过服务调用访问的简单实体框架存储库(请参阅 this UML diagram 查看相关课程)。我正在尝试按如下方式测试服务

public class ProductServiceTests
{
    private IProductService sut;
    private IProductRepository productRepo;
    [Fact]
    public async Task CanCallGetProductById()
    {
        //Arrange
        productRepo = new Fake<IProductRepository>().FakedObject;
        sut = new ProductService(productRepo);
        var id = 1;
        var expectation = new Product { Id = id };
        A.CallTo(() => sut.GetProductById(id)).Returns(expectation);

        // Act
        var result = await sut.GetProductById(id);

        // Assert
        Assert.Equal(id, result.Id);
    }
}

但是,这会导致以下异常System.ArgumentException : Object 'ProductService' of type ProductService is not recognized as a fake object.

这意味着我可能需要使用以下服务的模拟

 sut = new Fake<IProductService>().FakedObject;

然而,这种方法也存在问题:对函数await sut.GetProductById(id) 的调用实际上并没有调用任何被测代码。确实,如果我抛出异常,测试仍然通过

    public async Task<Product> GetProductById(int id)
    {
        throw new NotImplementedException();
        //return await _productRepository.GetAll().FirstOrDefaultAsync(x => x.Id == id);
    }

编辑 经过一些故障排除后,我意识到,实际上,我试图测试的代码实际上并没有达到。比如下面的测试

A.CallTo(() =&gt; productRepo.GetAll()).MustHaveHappened();

失败并显示Product.GetAll() Expected to find it once or more but no calls were made to the fake object.的消息

请有人解释我应该如何解决这个错误?

编辑

IProductRepositoryIProductService 定义如下:

public interface IProductRepository
{
    IQueryable<Product> GetAll();
}
public interface IProductService
{
    Task<Product> GetProductById(int id);
}

ProductService的实现方式如下

public class ProductService : IProductService
{
    private readonly IProductRepository _productRepository;

    public ProductService(IProductRepository productRepository)
    {
        _productRepository = productRepository;
    }
   

    public async Task<Product> GetProductById(int id)
    {
        //throw new NotImplementedException();
        return await _productRepository.GetAll()
                                        .FirstOrDefaultAsync(x => x.Id == id);
    }
}

【问题讨论】:

    标签: c# .net unit-testing fakeiteasy


    【解决方案1】:

    使用假冒(或模拟或其他)来帮助测试服务的一般方法是模拟被测系统的协作者(在本例中为IProductService),并使用真实被测系统(如果您不使用真实的被测系统,则您的系统不是“被测系统”)。因此,采用您的原始代码并进行调整,您会想要这样的东西:

    public class ProductServiceTests
    {
        private IProductService sut;
        private IProductRepository productRepo;
        [Fact]
        public async Task CanCallGetProductById()
        {
            //Arrange
            productRepo = new Fake<IProductRepository>().FakedObject;
            sut = new ProductService(productRepo);
            var id = 1;
            var expectation = new Product { Id = id };
    
    
            // This is different from your example. 
            // The idea is to configure `productRepo`, not `sut`.
            // Also, I don't know the interface for `IProductRepository`,
            // so you'll have to adjust this call to match `GetAll`
            A.CallTo(() => productRepo.GetAll(id)).Returns(new [] {expectation});
    
            // Act
            var result = await sut.GetProductById(id);
    
            // Assert
            Assert.Equal(id, result.Id);
        }
    }
    

    【讨论】:

    • 非常感谢您的解决方案。不幸的是,我无法根据我的代码调整您的答案(但我想这是因为我的无知)。事实上,我的存储库没有实现GetProductById 方法。它只实现了一个GetAllProducts 方法,`IProductService` 使用该方法通过其id 查找产品。我已在新编辑的问题中添加了此信息。
    • 我没有说存储库实现了GetProductById。我说要配置productRepo.GetAll。如果您配置GetAll(),我的方法应该有效,但需要注意的是您需要让它返回一个实现IDbAsyncQueryProvider 的类,这可能很难实现。我不是 EntityFramework 用户,所以建议关注 Ben Hall 的 Using FakeItEasy with Entity Framework 6。不过,由于您是 FIE 新手,因此首先练习测试不使用 EF 的代码变体可能会有所帮助。
    • 再次感谢您的帮助。我会听从你的建议,我相信我会解决的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-14
    • 2017-02-14
    • 2017-01-20
    • 1970-01-01
    相关资源
    最近更新 更多