Should Data Be Hard Coded Across All Unit Tests?

unit testing

Most unit testing tutorials/examples out there usually involve defining the data to be tested for each individual test. I guess this is part of the "everything should be tested in isolation" theory.

However I've found that when dealing with multitier applications with a lot of DI, the code required for setting up each test gets very long winded. Instead I've built a number of testbase classes which I can now inherit which has a lot of test scaffolding pre-built.

As part of this, I'm also building fake datasets which represent the database of a running application, albeit with usually only one or two rows in each "table".

Is it an accepted practice to predefine, if not all, then the majority of the test data across all the unit tests?

Update

From the comments below it does feel like I'm doing more integration than unit testing.

My current project is ASP.NET MVC, using Unit of Work over Entity Framework Code First, and Moq for testing. I've mocked the UoW, and the repositories, but I'm using the real business logic classes, and testing the controller actions. The tests will often check that the UoW has been committed, e.g:

[TestClass]
public class SetupControllerTests : SetupControllerTestBase {
  [TestMethod]
  public void UserInvite_ExistingUser_DoesntInsertNewUser() {
    // Arrange
    var model = new Mandy.App.Models.Setup.UserInvite() {
      Email = userData.First().Email
    };

    // Act
    setupController.UserInvite(model);

    // Assert
    mockUserSet.Verify(m => m.Add(It.IsAny<UserProfile>()), Times.Never);
    mockUnitOfWork.Verify(m => m.Commit(), Times.Once);
  }
}

SetupControllerTestBase is building the mock UoW, and instantiating the userLogic.

A lot of the tests require having an existing user or product in the database, so I've pre-populated what the mock UoW returns, in this example userData, which is just an IList<User> with a single user record.

Best Answer

Ultimately, you want to write as little code as possible to get as much outcome as possible. Having a lot of the same code in multiple tests a) tends to result in copy-paste coding and b) means that if a method signature changes you can end up having to fix a lot of broken tests.

I use the approach of having standard TestHelper classes that provide me with a lot of the data types that I routinely use, so I can create sets of standard entity or DTO classes for my tests to query and know exactly what I will get each time. So I can call TestHelper.GetFooRange( 0, 100 ) to get a range of 100 Foo objects with all their dependent classes/fields set.

Particularly where there are complex relationships configured in an ORM type system which need to be present for things to run correctly, but aren't necessarily significant for this test that can save a lot of time.

In situations where I'm testing close to the data level, I sometimes create a test version of my repository class that can be queried in a similar way (again this is in an ORM type environment, and it wouldn't be relevant against a real database), because mocking out the exact responses to queries is a lot of work and often only provides minor benefits.

There are some things to be careful of, though in unit tests:

  • Make sure your mocks are mocks. The classes that perform operations around the class being tested must be mock objects if you are doing unit testing. Your DTO/entity type classes can be the real thing, but if classes are performing operations you need to be mocking them- otherwise when the supporting code changes and your tests start failing, you have to search for a lot longer to figure out which change actually caused the problem.
  • Make sure you are testing your classes. Sometimes if one looks through a suite of unit tests it becomes apparent that half of the tests are actually testing the mocking framework more than the actual code that they are supposed to be testing.
  • Don't reuse mock/supporting objects This is a biggie- when one starts trying to get clever with code supporting unit tests it is really easy to inadvertently create objects that persist between tests, which can have unpredictable effects. For example, yesterday I had a test that passed when run on its own, passed when all tests in the class were run, but failed when the entire test suite was run. It turned out there was a sneaky static object way off in a test helper that, when I created it, would definitely never have caused a problem. Just remember: At the start of the test, everything is created, at the end of the test everything is destroyed.
Related Topic