我应该如何模拟这个简单的服务层方法?

时间:2011-02-10 13:35:16

标签: .net unit-testing testing mocking

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

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();
}

我的偏好是使用Moq,但我很高兴看到其他的瑕疵..主要是因为我没有遵循确切的语法答案,而是理论上的答案。 IE浏览器。你需要嘲笑IRepository<GameFile>并仅设置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?
}

2 个答案:

答案 0 :(得分:3)

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

根据我的理解,这是您的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();
}

答案 1 :(得分:0)

通过声明有关ToList()返回的值的信息,您似乎正在尝试测试实际方法Find()WhereIsActive()ToList()。如果是这样,您不需要使用模拟对象。

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

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

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

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);

如您所见,测试代码比测试代码更复杂。这是一个很好的迹象,表明您可能不需要测试。