There are some rules of thumb I encourage my team to follow when writing unit tests and they are as follows:
1. System under test should be clearly defined. A variable naming convention I have personally adopted is “sut” to stand for “system under test”.
2. Each test should abide by the AAA principle of Arrange, Act and Assert. There should be a clear demarcation between these three different aspects of a test.
3. As part of the Arrange phase, all dependencies of the system under test should be clearly defined, and mocks used where to abstract away external dependencies, whenever possible. Also, always best to mock via interfaces instead of abstract base classes. No container registrations here and no hidden dependency injection into the system under test.
4. As part of the Arrange phase, all additional dependencies such as file names or configuration items should be explicit and clearly defined. Avoid depending on external entities which will not guarantee consistency in your test results such as webservices, etc.
5. During the Acting phase, it should be clear which of the APIs of the system under test is being exercised and this method being exercised should typically return a response.
6. Assertion should be based upon the response of the method acted upon from the previous phase. Assert the response is what you expect. Avoid asserting that the method under test was called. For example, this is a good assertion:
// arrange var sut = new ResourceController(); // act var result = sut.GetResource(new Dictionary()).ToList(); // assert Assert.AreEqual(2, result.Count); Assert.AreEqual("Kanata", result.AdditionalProperties["placeofservicetypename"]);
Assuming the same entities defined in the Arrange section above,
resourceCacheMock.Verify(sut => sut.GetResources(new Dictionary().....)
The latter does not guarantee we get the correct results but rather the fact that some other resource was invoked, which means we need to know internally how sut.GetResource works.
8. Minimize the number of asserts in a test. I would not say a test should be restricted to one assert as this is an artificial constraint which breaks down too often. Instead, I would advocate for a minimalist approach, encouraging developers to ensure to think about splitting a test if the number of asserts is greater than 3 for example.
9. Keep tests small or digestable. It should be relatively straight forward to look at a test and determine what it is doing. If a colleague cannot decipher the purpose of a test within a couple of minutes, it is overly complex or doing too much.
10. Test names should be clearly indicate what the test is trying to accomplish.