【问题标题】:How should I be Mocking this simple Service layer method?我应该如何模拟这个简单的服务层方法?
【发布时间】:2011-02-10 13:35:16
【问题描述】:

我的服务层中有以下简单方法。我不确定如何从存储库开始模拟不同的链接部分?

public ICollection<GameFile> FindAllActiveGamesFiles()
{
    return _gameFileRepository // <- this is an IRepository<GameFile>
        .Find() // <-- returns an IQueryable<T> .. in this case, an IQueryable<GameFile>
        .WhereIsActive() // <-- a simple Pipe/Filter; 'where x.IsActive'
        .ToList();
}

我的偏好是使用起订量,但我很高兴看到其他的暗示.. 主要是因为我不是在追求确切的语法答案,而是在理论上的答案。 IE。你只需要模拟IRepository&lt;GameFile&gt; 并设置ToList() 方法.. 等等等等...

这就是我不明白我应该嘲笑的东西。

干杯:)

--- 更新:澄清我的问题

我要测试的是“FindAllActiveGamesFiles()”方法是否有效。因此,我相信我需要模拟 gameFileRepository (这是一个接口)。我不确定这是否是我在这个例子中唯一应该嘲笑的事情。

例如。

[TestMethod]
public void MyTest()
{
    // Arrange.
    Mock<IRepository<GameFile>> mockRepository = new Mock<IRepository<GameFile>>();
    mockRepository.Setup(....).MoreStuffToDo(...); // <-- that's what i'm unsure about.
    IGameFileService = new GameFileService(mockRepository, fakeLoggingService);

    // Act.
    var gameFiles = gameFileService.FindAllActiveGamesFiles();

    // Asserts.
    Assert.IsNotNull(gameFiles);
    CollectionAssert.AllItemsAreNotNull(gameFiles.ToArray());
    // .. and more asserts ///
    // What about expectations? eg. that ToList() was entered/called?
}

【问题讨论】:

  • 我很困惑!您不模拟方法,而是模拟类型/接口。那么您的意思是如何为单元测试设定期望值?
  • 听起来你想通过模拟各种调用来单元测试这个方法。我的看法是,上面的例子太简单了,无法测试。如果您的测试代码最终比被测代码更复杂,那么您就是在浪费时间。用它来直观地验证方法并继续前进。
  • 如果你有类似的,更复杂的链式方法来模拟,你基本上需要为每个返回的对象创建一个模拟,只有一个期望(如果链中的每个方法返回一个不同的对象,就像你在这里)。它可以是一个真正的 PITA 来设置和验证,因此只有在传递给各个方法调用的参数很复杂并且需要测试时才会得到回报。不要浪费时间测试 userName 参数是否已传递给 byUserName()
  • @David Harkness & @Aliostad - 我更新了 OP 以进一步解释我的问题。我知道上面的场景可能是悔恨的,但它仍然是一个真实的例子,我觉得我仍然需要测试这个方法。
  • 您到底在 FindAllActiveGamesFiles() 内部测试什么?它只是调用 _gameFileRepository.Find().WhereIsActive().ToList();您是否已经在测试这些方法中的每一个?如果是这样,那么你就完成了。 ;)

标签: .net unit-testing testing mocking


【解决方案1】:

听起来您想编写一个测试来展示您的 Linq 语句的覆盖范围。您已经指出您的存储库是一个接口 (IRepository) 并且应该被模拟。您只需要一些示例来说明您的服务层如何正确过滤存储库的内容。

据我所知,这是您的 ServiceLayer。

public class ServiceLayer
{
     private readonly IRepository<GameFile> _gameRepository;

     public SerivceLayer(IRepository<GameFile> repository)
     {
         _gameRepository = repository;
     }

     public IEnumerable<GameFile> FindAllActiveGamesFiles()
     {
         return _gameRepository
                    .Find()  // method we need to mock
                    .Where( gameFile => gameFile.IsActive)
                    .ToList();
     }
}

让我们编写一些测试......(NUnit 和 Moq)

[TestFixture]
public class ServiceLayerFixture
{
      protected IRepository<GameFile> Repository;
      protected ServiceLayer Subject;
      protected ICollection<GameFile> Results;

      [Setup]
      public void Setup()
      {
          // create our mock
          Repository = new Mock<IRepository<GameFile>>().Object;

          // initialize our test subject
          Subject = new ServiceLayer(Repository);
      }

      [Test]
      public void WhenRepositoryDoesNotContainItems_ServiceLayer_ReturnsAnEmptyCollection()
      {
         Mock.Get(Repository)
             .Setup( r => r.Find())
             .Returns( new List<GameFile>().AsQueryable() );

         Results = Subject.FindAllActiveGameFiles();

         Assert.AreEqual(0, Results.Count);
      }

      [Test]
      public void WhenRepositoryDoesNotContainActiveItems_ServiceLayer_ReturnsAnEmptyCollection()
      {
         Mock.Get(Repository)
             .Setup( r => r.Find())
             .Returns( 
                 new List<GameFile>()
                     {
                        new GameFile { IsActive = false },
                        new GameFile { IsActive = false }
                     }.AsQueryable() );

         Results = Subject.FindAllActiveGameFiles();

         Assert.AreEqual(0, Results.Count);            
      }

      [Test]
      public void WhenRepositoryContainActiveItems_ServiceLayer_FiltersItemsAppropriately()
      {
         Mock.Get(Repository)
             .Setup( r => r.Find())
             .Returns( 
                 new List<GameFile>()
                     {
                        new GameFile { IsActive = true },
                        new GameFile { IsActive = false }
                     }.AsQueryable() );

         Results = Subject.FindAllActiveGameFiles();

         Assert.AreEqual(1, Results.Count);            
      }
}

您的代码不足之处在于您可以更优雅地处理 IRepository 中的异常。

所以考虑一下:

[Test]
public void WhenTheRepositoryFails_ServiceLayer_ShouldHandleExceptionsGracefully()
{
    Mock.Get(Repository)
        .Setup( r => r.Find())
        .Throws( new InvalidOperationException() );

    Results = Subject.FindAllActiveGameFiles();

    Assert.AreEqual(0, Results.Count);          
}

或者,也许您想包装异常?

[Test]
[ExpectedException(typeof(GameFileNotFoundException))]
public void WhenTheRepositoryFails_ServiceLayer_ShouldReportCustomError()
{
    Mock.Get(Repository)
        .Setup( r => r.Find())
        .Throws( new InvalidOperationException() );

    Subject.FindAllActiveGameFiles();
}

【讨论】:

  • 非常感谢。这是一个很好的答案,有各种深入的例子。我希望我能给你更多的分数。干得好,谢谢,伙计!
  • 有没有办法单独测试WhereIsActive()?随着您添加更多查询方法(FindAllActiveGameFilesOlderThan(date)FindAllActiveGameFilesOwnedBy(user) 等),具有重复代码的测试方法的数量将显着增加,并且您将多次测试相同的代码路径。
  • 我只是简化了示例来说明需要模拟的是 Find 方法。一旦有了 IQueryable,扩展方法就可以正常工作。功能优势 FTW!
【解决方案2】:

通过断言从ToList() 返回的值,您似乎正在尝试测试实际方法Find()WhereIsActive()ToList()。如果是这样,您就不需要使用模拟对象。

相反,我会为每个方法编写单独的测试。这是健全的单元测试的关键。如果可以的话,FindAllActiveGamesFiles() 的测试应该避免测试它们。如果可能,您应该只测试FindAllActiveGamesFiles() 中的代码。

接下来,FindAllActiveGamesFiles() 的测试需要为mockRepository 设置期望值。假设 Find()WhereIsActive() 都返回相同的存储库,您将模拟设置为这些方法的返回值。

我不知道你的模拟库的 API,但这是它在伪代码中的样子:

Mock<IRepository<GameFile>> mockRepository = new Mock<IRepository<GameFile>>();
List<GameFile> result = new List<GameFile>();
mockRepository.expect('Find')->willReturn(mockRepository);
mockRepository.expect('WhereIsActive')->willReturn(mockRepository);
mockRepository.expect('ToList')->willReturn(result);
IGameFileService service = new GameFileService(mockRepository, fakeLoggingService);

assertSame(service.FindAllActiveGamesFiles(), result);

如您所见,测试代码比被测试代码更复杂。这是您可能不需要测试的好兆头。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-12-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-01
    • 1970-01-01
    • 2022-01-05
    相关资源
    最近更新 更多