使用Moq来模拟返回值的存储库

8

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

目前为止,这是我所拥有的:

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. First test, I need to tell the setup to return an int of 1.
  2. I need to create a second test Insert_Duplicate_Country_Throws_Exception(). In my Setup, how do I tell the repository to throw an error when I do:

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

框架:

  1. NUnit。
  2. Moq。
  3. ASP.NET MVC - 存储库模式。
2个回答

8
你的第一次测试应该像这样:

你的第一次测试应该看起来像这样:

[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 Mclean
@Shawn:一般来说,模拟对象的设计旨在让开发人员独立测试应用程序层。在这种情况下,您正在测试服务层,而不是DAO。您应该做的就是检查服务层是否确实调用了正确的方法。存储库单元测试应该负责验证在应该抛出异常时是否抛出了异常。 - Tyler Treat
好的,这很有道理。因此,实质上,我并不需要为该服务进行第二次测试,只需要模拟存储库。谢谢。 - Shawn Mclean
没问题。如果想要更好地理解这个思路,请阅读这里的解释:https://dev59.com/u3A65IYBdhLWcg3wzyBl#3623574 - Tyler Treat
它是真的,模拟对象旨在独立测试应用程序层,我同意这一点。但在实际情况下,这并不会发生,开发人员想要测试完整的方法以及其依赖项,因此我认为模拟在这种情况下不太合适。并且我们应该为那些维护成本高的方法编写模拟,例如数据库调用等。 - Shobhit Walia

1

我认为你可能稍微误解了在你提供的两种情况下使用模拟测试的目的。

在第一种情况下,你希望测试当你传入“牙买加”时是否返回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.");
}

在第二种情况下,您希望测试当您尝试添加重复的国家时是否会引发异常。使用模拟对象进行这个测试没有什么意义,因为您只会测试到模拟对象在添加重复项时的行为,而不是真实的实现。

谢谢。我理解了不检查值的部分。在你的例子中,你没有返回一个值(CountryId)。在AddCountry方法中,它返回存储库的Id。因为需要,我是否仍然应该模拟返回值? - Shawn Mclean
@Shawn 对不起,这边时间有点晚了...是的,应该在那里模拟返回值。我已经更新了我的答案。 - Tim Lloyd
谢谢。有更多的例子,我就越能理解这些模式 :) - Shawn Mclean
这可以通过使用AutoFixture和Moq自定义来进一步简化。 - John Zabroski

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接