Stub Vs Fake Vs Spy Vs Mock

Introduction

 
We always get confused with the meaning of Mock, Stub, Fake and Spy. I have tried to explain the meaning of these terms and differences between them in some simple ways for beginners.
 
All of these concepts are mainly used to replace the code dependencies with some alternatives so, that we can concentrate on the unit we are testing while writing in-memory tests.
 

Stub

  • A Stub is the lightest and most static version of this chain.
  • Stub always returns the predefined output regardless of the input.
  • We can't control the behavior of the stub.
  • A stub can be useful to mimic the database objects (as shown below).
Below is the stubbed version of an active directory user store where GetUserRole() and GetAllUsers() functions always return the same output regardless of the input. 
  1. public class StubUserStore : IUserStore  
  2. {  
  3.     public string GetUserRole(string username)  
  4.     {  
  5.         return "contributor";  
  6.     }  
  7.   
  8.     public List<UserDetail> GetAllUsers()  
  9.     {  
  10.         return new List<UserDetail>()  
  11.         {  
  12.             new UserDetail{ Role = "administrator", Name = "admin"},  
  13.             new UserDetail(){ Role = "contributor", Name = "User 1"}  
  14.         };  
  15.     }      
  16. }  
  17. public interface IUserStore  
  18. {  
  19.     string GetUserRole(string username);  
  20. }  
  21.   
  22. public class UserDetail  
  23. {  
  24.     public string Name { getset; }  
  25.     public string Role { getset; }  
  26. }  

Fake

  • A Fake is more powerful than Stub.
  • Fake classes can change the behavior based on input.
  • Fake class functions can return different output for different inputs unlike that of stub.
  • Fakes can help us to mimic all the possible behavior of the interfaces.
Below is the fake version of the same user store where the behavior of the GetUserStore() function can change based on input.
  1. public class FakeUserStore : IUserStore  
  2. {  
  3.     public string GetUserRole(string username)  
  4.     {  
  5.         if (username == "admin")  
  6.             return "administrator";  
  7.         else  
  8.         return "contributor";  
  9.     }      
  10. }   
  11. public interface IUserStore  
  12. {  
  13.     string GetUserRole(string username);  
  14. }  

Spy

  • A Spy is an advanced version of the Fake which can store the previous state of the object.
  • The spy can be useful to mimic the retry services or to check scenarios like 'if the function called at least once'.
  • You can also create a spy for loggers to store and validate all the logs logged while running the test case.
As shown below, SpyUserStore maintains the state of the number of times a function got called and throws an exception if the function gets called more than once within the scope of the test case.
  1. public class SpyUserStore : IUserStore  
  2. {  
  3.     private static int Counter { getset; }  
  4.   
  5.     public SpyUserStore()  
  6.     {  
  7.         Counter = 0;  
  8.     }  
  9.   
  10.     public string GetUserRole(string username)  
  11.     {  
  12.   
  13.         if (Counter >= 1)  
  14.             throw new Exception("Function called more than once");  
  15.   
  16. Counter++;  
  17.        
  18.     if (username == "admin")  
  19.             return "administrator";  
  20.         else  
  21.             return "contributor";     
  22.      }  
  23. }   

Mock

  • A Mock is the most powerful and flexible version in the chain.
  • The behavior of the mocked interface can be changed dynamically based on scenarios.
  • We can apply a variety of assertions by creating Mocked objects using mock frameworks, for example - Moq.
  • Mock gives the full control over the behavior of mocked objects.
As shown below, I have set up the mock object of IUserStore which behaves differently for different inputs.
  1. Mock<IUserStore> mockedUserStore=new Mock<IUserStore>();  
  2.                 mockedUserStore.Setup(func => func.GetUserRole("admin")).Returns("administrator");  
  3.                 mockedUserStore.Setup(func => func.GetUserRole("user1")).Returns("contributor");  
  4.                 mockedUserStore.Setup(func => func.GetUserRole("user2")).Returns("basic");  

Which one shall I use?

  • I would recommend starting with the lightest implementation first.
  • Try to avoid mocks if the same scenarios can be reproduced with simple stubs and fakes.
  • Use Stub to represent database objects and use Fake and Spy to mimic the behavior of business interfaces or services like retry, logging, etc.
  • Mocks sometimes make test cases difficult to read and difficult to understand.
  • Improper use of Mock may impact test strategy in a negative way