Search code examples
c#unit-testingservice-layer

Unit Test Crossing Layer Boundaries


I have a unit test in which I'm testing that my Service class inserts data into the correct place in my Repository class under various conditions. My Repository class has three "storage" locations, and my Service class has a single public AddData method which conditionally adds data to one of the three storages (which are different sets of database tables in production) within the repository. Is it OK to have my unit test look at the service's repository to make sure the data got added in the correct place? For example:

The service class looks like this:

class MyService
{
   private readonly IRepository Repository;       

   // Add data to a different place in the repository based on the value of the 'someSwitch' parameter
   public void AddData(MyDataObject data, int someSwitch)
   {   
      if (someSwitch >= 0 && someSwitch < 10)
      { 
         this.Repository.AddToStorageOne(data);
      }
      else if (someSwitch >= 10 && someSwitch < 20)
      {
         this.Repository.AddToStorageTwo(data);
      }
      else
      {
         this.Repository.AddToStorageThree(data);
      }
   }
}

The unit test looks like this:

[TestClass]
public class MyTests
{
   [TestMethod]
   private void AddData_SwitchValueOf15_ShouldAddToStorageTwo()
   {
      // Create the MyService class with an in-memory repository
      MockRepository repository = new MockRepository();
      MyService service = new MyService
      {
         Repository = repository
      };

      MyDataObject myDataObject = new MyDataObject();
      // Assign some data to myDataObject

      // This should insert myDataObject into StorageTwo
      service.AddData(myDataObject, 15);

      // Here I want to assert that the data got added to "StorageTwo" in the repository
   }
}

Now I want to test that the data was inserted into StorageTwo of the repository. Of course, it would be easy for me to do something like

Assert.AreEqual(0, repository.StorageOne.Count);
Assert.AreEqual(1, repository.StorageTwo.Count);
Assert.AreEqual(0, repository.StorageThree.Count);

So my question is, is it OK for my unit test (which is testing a Service method) to look into the service's repository like this? If doing so is bad practice, how should I go about checking that the data was inserted into the correct place in the repository? My Service class only has a single public GetData() method, which combines data from StorageOne, StorageTwo, and StorageThree, so the Service class doesn't have any public methods which can look into the individual storages.


Solution

  • In a unit test, you should not really go beyond the boundary of your class. A way to do this is to mock every dependency. Once they're mocked, you can verify the interactions your class have with the outside world.

    This can also help to reduce the code written for the tests only. For example, you had to expose the Count of your storage for your test in your scenario. It is not necessary if you trust blindly the repo to do its job (as it should be unit tested too) and just check that you call it correctly.

    In your specific case, you could mock your Repository and then assert that the correct method was called. As an added security, you could validate that the others weren't.

    With Moq, it would look like this:

    [TestClass]
    public class MyTests
    {
       [TestMethod]
       private void AddData_SwitchValueOf15_ShouldAddToStorageTwo()
       {
          // Mock the repository then add it to the service
          Mock<IRepository> mockRepository = new Mock<IRepository>();
    
          MyService service = new MyService
          {
             Repository = mockRepository 
          };
    
          MyDataObject myDataObject = new MyDataObject();
          // Assign some data to myDataObject
    
          // This should insert myDataObject into StorageTwo
          service.AddData(myDataObject, 15);
    
          // Check that the correct method was called once, with our parameter
          mockRepository.Verify(r => r.AddToStorageTwo(myDataObject), Times.Once());
    
          // Check that the other methods were never called, with any input
          mockRepository.Verify(r => r.AddToStorageOne(It.IsAny<MyDataObject>()), Times.Never());
          mockRepository.Verify(r => r.AddToStorageThree(It.IsAny<MyDataObject>()), Times.Never());
       }
    }