【问题标题】:Mock lazy interface with Moq使用 Moq 模拟惰性接口
【发布时间】:2016-08-14 21:44:43
【问题描述】:

我想要模拟惰性接口,但我得到了object reference not set to an instance of an object 异常。

‌这是正在测试的课程:

public class ProductServiceService : IProductServiceService
{
    private readonly Lazy<IProductServiceRepository> _repository;
    private readonly Lazy<IProductPackageRepository> _productPackageRepository;

    public ProductServiceService(
        Lazy<IProductServiceRepository> repository,
        Lazy<IProductPackageRepository> productPackageRepository)
    {
        _repository = repository;
        _productPackageRepository = productPackageRepository;
    }

    public async Task<OperationResult> ValidateServiceAsync(ProductServiceEntity service)
    {
        var errors = new List<ValidationResult>();

        if (!await _productPackageRepository.Value.AnyAsync(p => p.Id == service.PackageId))
            errors.Add(new ValidationResult(string.Format(NameMessageResource.NotFoundError, NameMessageResource.ProductPackage)));

       .
       .
       .

        return errors.Any()
            ? OperationResult.Failed(errors.ToArray())
            : OperationResult.Success();
    }
}

这里是测试类

[Fact, Trait("Category", "Product")]
public async Task Create_Service_With_Null_Financial_ContactPerson_Should_Fail()
{
    // Arrange
    var entity = ObjectFactory.Service.CreateService(packageId: 1);

    var fakeProductServiceRepository = new Mock<Lazy<IProductServiceRepository>>();

    var repo= new Mock<IProductPackageRepository>();
    repo.Setup(repository => repository.AnyAsync(It.IsAny<Expression<Func<ProductPackageEntity, bool>>>()));
    var fakeProductPackageRepository  = new Lazy<IProductPackageRepository>(() => repo.Object);

    var sut = new ProductServiceService(fakeProductServiceRepository.Object, fakeProductPackageRepository);

    // Act
    var result = await sut.AddServiceAsync(service);

    // Assert
    Assert.False(result.Succeeded);
    Assert.Contains(result.ErrorMessages, error => error.Contains(string.Format(NameMessageResource.NotFoundError, NameMessageResource.ProductPackage)));
}

fakeProductPackageRepository 始终为空。我关注了这篇博文,但仍然收到空引用异常。

How to mock lazy initialization of objects in C# unit tests using Moq

更新: 这是一个指示fakeProductPackageRepository 为空的屏幕。

【问题讨论】:

  • 显示sut.AddServiceAsync 的实现。它不是在不完整的例子中。请提供minimal reproducible example
  • fakeProductPackageRepository 不应该是 null,因为在你的例子中你 new 在将它注入到 sut 之前就上升了。
  • 从您上次更新开始:fakeProductPackageRepository 不是 nullfakeProductPackageRepository 的内部ValuenullLazy&lt;T&gt;.Value 只会在您第一次访问时实现。您还会注意到IsValueCreatedfalse。那是因为那时没有调用 Value 来调用工厂函数。
  • 我添加了另一个屏幕截图,显示在方法中调用 repo 也是空的。

标签: c# unit-testing mocking moq


【解决方案1】:

这是您的示例的重构版本:

[Fact, Trait("Category", "Product")]
public async Task Create_Service_With_Null_Financial_ContactPerson_Should_Fail() {
    // Arrange
    var entity = ObjectFactory.Service.CreateService(packageId = 1);

    var productServiceRepositoryMock = new Mock<IProductServiceRepository>();

    var productPackageRepositoryMock = new Mock<IProductPackageRepository>();
    productPackageRepositoryMock
        .Setup(repository => repository.AnyAsync(It.IsAny<Expression<Func<ProductPackageEntity, bool>>>()))
        .ReturnsAsync(false);

    //Make use of the Lazy<T>(Func<T>()) constructor to return the mock instances
    var lazyProductPackageRepository = new Lazy<IProductPackageRepository>(() => productPackageRepositoryMock.Object);
    var lazyProductServiceRepository = new Lazy<IProductServiceRepository>(() => productServiceRepositoryMock.Object);

    var sut = new ProductServiceService(lazyProductServiceRepository, lazyProductPackageRepository);

    // Act
    var result = await sut.AddServiceAsync(service);

    // Assert
    Assert.False(result.Succeeded);
    Assert.Contains(result.ErrorMessages, error => error.Contains(string.Format(NameMessageResource.NotFoundError, NameMessageResource.ProductPackage)));
}

更新

您所述问题的以下Minimal, Complete, and Verifiable example 经测试通过。

[TestClass]
public class MockLazyOfTWithMoqTest {
    [TestMethod]
    public async Task Method_Under_Test_Should_Return_True() {
        // Arrange
        var productServiceRepositoryMock = new Mock<IProductServiceRepository>();

        var productPackageRepositoryMock = new Mock<IProductPackageRepository>();
        productPackageRepositoryMock
            .Setup(repository => repository.AnyAsync())
            .ReturnsAsync(false);

        //Make use of the Lazy<T>(Func<T>()) constructor to return the mock instances
        var lazyProductPackageRepository = new Lazy<IProductPackageRepository>(() => productPackageRepositoryMock.Object);
        var lazyProductServiceRepository = new Lazy<IProductServiceRepository>(() => productServiceRepositoryMock.Object);

        var sut = new ProductServiceService(lazyProductServiceRepository, lazyProductPackageRepository);

        // Act
        var result = await sut.MethodUnderTest();

        // Assert
        Assert.IsTrue(result);
    }

    public interface IProductServiceService { }
    public interface IProductServiceRepository { }
    public interface IProductPackageRepository { Task<bool> AnyAsync();}

    public class ProductServiceService : IProductServiceService {
        private readonly Lazy<IProductServiceRepository> _repository;
        private readonly Lazy<IProductPackageRepository> _productPackageRepository;

        public ProductServiceService(
            Lazy<IProductServiceRepository> repository,
            Lazy<IProductPackageRepository> productPackageRepository) {
            _repository = repository;
            _productPackageRepository = productPackageRepository;
        }

        public async Task<bool> MethodUnderTest() {
            var errors = new List<ValidationResult>();

            if (!await _productPackageRepository.Value.AnyAsync())
                errors.Add(new ValidationResult("error"));

            return errors.Any();
        }
    }
}

【讨论】:

  • 我只是在AddServiceAsync 方法中使用IProductPackageRepository。我的问题是lazyProductPackageRepository 为空,lazyProductServiceRepository 工作正常。
  • 您是否尝试过我回答中的代码。您的示例中有错误,即:var fakeProductPackageRepository = new Lazy&lt;IProductPackageRepository&gt;(() =&gt; fakeProductPackageRepository.Object);。那甚至不应该让你编译。您也不要使用您创建的 repo 模拟。
  • 所以你应该有var fakeProductPackageRepository = new Lazy&lt;IProductPackageRepository&gt;(() =&gt; repo.Object);
  • @MohsenEsmailpour,我查看了您的代码并向您展示了您在哪里犯了错误。
  • 抱歉,这是复制粘贴错误。问题已更新。
【解决方案2】:

Lazy&lt;&gt; 作为参数有点出乎意料,尽管不是非法的(显然)。请记住,包裹在服务周围的Lazy&lt;&gt; 实际上只是延迟执行工厂方法。为什么不直接将工厂传递给构造函数?您仍然可以在实现类中将对工厂的调用包装在 Lazy&lt;&gt; 中,但随后您可以在测试中伪造/模拟您的工厂并将其传递给您的 sut

或者,您传递Lazy&lt;&gt; 的原因可能是因为您实际上是在处理单身人士。在这种情况下,我仍然会创建一个工厂并依赖IFactory&lt;&gt;。然后,工厂实现可以在其中包含Lazy&lt;&gt;

通常,我通过在 IoC 容器中为依赖项设置自定义对象范围来解决单例需求(没有延迟加载)。例如,StructureMap 可以很容易地在 Web 应用程序中将某些依赖项设置为单例或按请求范围。

我很少需要断言我已经对被测系统内的某些服务进行了延迟初始化。我可能需要验证我是否在某个范围内只初始化了一次服务,但这仍然可以通过伪造工厂接口轻松测试。

【讨论】:

    【解决方案3】:

    问题是您正在创建一个 Lazy 的 Mock 作为 fakeProductServiceRepository 并且稍后返回那个只需要一个 Mock 的实例。

    你应该改变

    var fakeProductServiceRepository = new Mock&lt;Lazy&lt;IProductServiceRepository&gt;&gt;();

    var fakeProductServiceRepository = new Mock&lt;IProductServiceRepository&gt;();

    【讨论】:

    • ProductServiceService 依赖于 Lzay ProductServiceRepository 接口。
    猜你喜欢
    • 1970-01-01
    • 2014-09-28
    • 1970-01-01
    • 2011-09-03
    • 2021-10-11
    • 2011-11-13
    • 1970-01-01
    • 1970-01-01
    • 2013-01-16
    相关资源
    最近更新 更多