【问题标题】:Using Moq to mock a repository that returns a value使用 Moq 模拟返回值的存储库
【发布时间】:2011-05-27 17:49:49
【问题描述】:

如何在模拟接受对象的存储库上设置我的测试方法?

这是我目前所拥有的:

Service.cs

    public int AddCountry(string countryName)
    {
        Country country = new Country();
        country.CountryName = countryName;
        return geographicsRepository.SaveCountry(country).CountryId;
    }

test.cs

    [Test]
    public void Insert_Country()
    {
        //Setup
        var geographicsRepository = new Mock<IGeographicRepository>();

        geographicsRepository.Setup(x => x.SaveCountry(It.Is<Country>(c => c.CountryName == "Jamaica"))); //How do I return a 1 here?

        GeographicService geoService = new GeographicService(geographicsRepository.Object);

        int id = geoService.AddCountry("Jamaica");

        Assert.AreEqual(1, id);
    }

SaveCountry(Country country); 返回一个整数。

我需要做两件事:

  1. 第一次测试,我需要告诉设置返回 1 的 int。
  2. 我需要创建第二个测试Insert_Duplicate_Country_Throws_Exception()。在我的设置中,我如何告诉存储库在我这样做时抛出错误:

    int id = geoService.AddCountry("Jamaica");
    int id = geoService.AddCountry("Jamaica");
    

框架:

  1. NUnit。
  2. 起订量。
  3. ASP.NET MVC - 存储库模式。

【问题讨论】:

    标签: unit-testing nunit moq


    【解决方案1】:

    您的第一个测试应该如下所示:

    [Test]
    public void Insert_Country()
    {
        Mock<IGeographicRepository> geographicsRepository = new Mock<IGeographicRepository>();
        GeographicService geoService = new GeographicService(geographicsRepository.Object);
    
        // Setup Mock
        geographicsRepository
            .Setup(x => x.SaveCountry(It.IsAny<Country>()))
            .Returns(1);
    
        var id = geoService.AddCountry("Jamaica");
    
        Assert.IsInstanceOf<Int32>(id);
        Assert.AreEqual(1, id);
        geographicsRepository.VerifyAll();
    }
    

    第二个测试应该是这样的:

    [Test]
    public void Insert_Duplicate_Country_Throws_Exception()
    {
        Mock<IGeographicRepository> geographicsRepository = new Mock<IGeographicRepository>();
        GeographicService geoService = new GeographicService(geographicsRepository.Object);
    
        // Setup Mock
        geographicsRepository
            .Setup(x => x.SaveCountry(It.IsAny<Country>()))
            .Throws(new MyException());
    
        try
        {
            var id = geoService.AddCountry("Jamaica");
            Assert.Fail("Exception not thrown");
        }
        catch (MyException)
        {
            geographicsRepository.VerifyAll();
        }
    }
    

    【讨论】:

    • 我不应该调用两次AddCountry("Jamaica") 并设置存储库来观察相同的字符串是否被传递了两次?
    • @Shawn:一般来说,模拟对象旨在允许开发人员独立测试应用程序层。在这种情况下,您正在测试独立于您的 DAO 的服务层。您应该做的就是检查服务层是否确实调用了正确的方法。据推测,存储库单元测试应该负责验证是否在应该抛出异常的时候抛出异常。
    • 好的,这是有道理的。所以本质上,我真的不需要对该服务进行第二次测试,只需要模拟存储库。谢谢。
    • 没问题。要对这一思路进行很好的解释,请在此处阅读:stackoverflow.com/questions/3622455/…
    • 它真正的mock对象是为了独立测试应用层而设计的,我同意这一点。但在实际情况下它不会发生,开发人员想要测试完整的方法及其依赖项,因此我认为在这种情况下模拟不适合。而且我们应该为那些维护成本高的方法编写模拟,即。 dbcall等
    【解决方案2】:

    我认为您可能稍微误解了在您提供的两种场景中使用模拟进行测试的目的。

    在第一个场景中,您希望测试当您传入“Jamaica”时是否返回 1。这不是模拟测试用例,而是真实行为的测试用例,因为您希望针对预期输出(即“牙买加”-> 1)测试特定输入。在这种情况下,模拟对于确保您的服务在内部调用 SaveCountry 更有用具有预期国家/地区的存储库,并从调用中返回值。

    设置您的“SaveCountry”案例,然后在您的模拟上调用“VerifyAll”是关键。这将断言“SaveCountry”确实是用国家“牙买加”调用的,并且返回了预期的值。通过这种方式,您可以确信您的服务已按预期连接到您的存储库。

    [Test]
    public void adding_country_saves_country()
    {
        const int ExpectedCountryId = 666;
    
        var mockRepository = new Mock<IGeographicRepository>();
    
        mockRepository.
          Setup(x => x.SaveCountry(It.Is<Country>(c => c.CountryName == "Jamaica"))).
          Returns(ExpectedCountryId); 
    
        GeographicService service= new GeographicService(mockRepository.Object);
    
        int id = service.AddCountry(new Country("Jamaica"));
    
        mockRepo.VerifyAll();
    
        Assert.AreEqual(ExpectedCountryId, id, "Expected country id.");
    }
    

    在第二种情况下,您希望测试在尝试添加重复国家/地区时是否引发了异常。使用 mock 执行此操作没有多大意义,因为您将测试的只是您的 mock 在添加重复项时具有行为,而不是您的实际实现。

    【讨论】:

    • 谢谢。我了解不检查值的部分。在您的示例中,您没有返回值(CountryId)。在 AddCountry 的 Method 中,它返回存储库的 Id。我还应该因为需要而模拟返回值吗?
    • @Shawn 我的道歉 - 在这里迟到了......是的,应该在那里模拟返回值。我已经更新了我的答案。
    • 谢谢。例子越多,我就越了解这些模式:)
    • 这可以通过 AutoFixture 和 AutoFixture 的 Moq 自定义进一步清理。
    猜你喜欢
    • 2011-05-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-28
    • 1970-01-01
    • 2021-12-08
    相关资源
    最近更新 更多