Lesson 5
Bringing Writing Tests and Mocks Together
Introduction

Welcome to the final lesson of the "Increasing Code Test Coverage" course! Throughout our journey, we've examined the significance of code coverage, the function of characterization tests, and how interfaces and mocks can improve testability. Now, we'll integrate these concepts to achieve thorough code coverage by combining test writing and mocking techniques. This lesson will help us solidify our understanding and prepare us for practical application.

Whole Problem: The UserProcessor Class

Let's examine an implementation of a user processor. We want to add tests to describe the characteristics of the system. We also don't want our tests to depend on a database. Let's take a closer look at the implementation of the UserProcessor class:

C#
1public class UserProcessor 2{ 3 private readonly UserDatabase _userDatabase = new UserDatabase(); 4 5 public bool UpdateUserLoginDate(int userId) 6 { 7 try 8 { 9 var user = _userDatabase.GetUserById(userId); 10 if (user == null) 11 return false; 12 13 user.LastLoginDate = DateTime.UtcNow; 14 _userDatabase.UpdateUser(user); 15 return true; 16 } 17 catch (Exception) 18 { 19 return false; 20 } 21 } 22}

The class uses a UserDatabase class as a dependency. This class can be viewed below:

C#
1public class UserDatabase 2{ 3 public User GetUserById(int userId) 4 { 5 Console.WriteLine("[Database Operation] Should not happen in tests!"); 6 return new User { Id = userId }; 7 } 8 9 public void UpdateUser(User user) 10 { 11 Console.WriteLine("[Database Operation] Should not happen in tests!"); 12 } 13} 14 15public class User 16{ 17 public int Id { get; set; } 18 public string Name { get; set; } 19 public string Email { get; set; } 20 public DateTime LastLoginDate { get; set; } 21}
Practice approach

First, we will focus on adding an interface for the database layer to enable us to mock this particular dependency. After that, we will focus on writing a collection of tests that will describe the system and help us increase code test coverage for the UserProcessor class.

Best Practices for Test Writing

When writing tests with mocks, consider the following best practices:

  • Focus on Behavior: Ensure tests verify the behavior of the code, not the implementation details.
  • Use Descriptive Names: Name tests clearly to indicate the scenario being tested.
  • Verify Interactions: Use mock verifications to ensure the correct methods are called.
  • Handle Exceptions: Test how the code handles exceptions to ensure robustness.

By following these practices, we can create reliable and maintainable tests that provide confidence in our code.

Practice Preparation

As we move on to the practice exercises, we'll have the opportunity to apply all the things we have learned so far in this course. Good luck, and enjoy the journey of mastering code test coverage!

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.