Implementing a fake repository

Lately I am using fake objects more than the other common kinds of doubles - stubs, spies and mocks. This is quite recent, I used to prefer stubs, spies or mocks. I find that fakes make my tests less fragile in the case of outside-in development when it comes to changes in the design. The problem is that there could be defects in the implementation of the fake objects. A tricky one may appear in the case of in-memory repositories:

  1. public class InMemoryRepo : JobRepository {
  2. readonly List jobs = new List();
  3. public void Add(Job job)
  4. {
  5. jobs.Add(job);
  6. }
  7. public void Update(Job job)
  8. {
  9. // TODO
  10. }
  11. public List FindBy(string ownerId)
  12. {
  13. return jobs.Where(job => job.IsOwnedBy(ownerId)).ToList();
  14. }
  15. }

This implementation is misleading. If we are test-driving a service expecting it to update a Job instance, we may forget to invoke the repository's Update method and it would still work.

  1. [TestClass]
  2. public class ServiceTest {
  3. [Test]
  4. public void AssignsOwnerToJob()
  5. {
  6. var service = Factory.CreateJobServiceWithFakeRepository();
  7. var job = service.CreateJob(irrelevantTitle);
  8.  
  9. service.Assign(job, irrelevantOwnerId);
  10.  
  11. service.JobsOwnedBy(irrelevantOwnerId).Should().HaveCount(1);
  12. }
  13. }
  14.  
  15. public class Service {
  16. private JobRepository repo {get; set;}
  17.  
  18. public Service(JobRepository repo){
  19. this.repo = repo;
  20. }
  21. public Job CreateJob(string title)
  22. {
  23. var job = new Job();
  24. repo.Add(job);
  25. /*...*/
  26. return job;
  27. }
  28. public void Assign(Job job, string ownerId){
  29. /*...*/
  30. job.Assign(ownerId);
  31. /* BUG: am forgetting the call to "repo.Update(job)" right here */
  32. }
  33. public List JobsOwnedBy(string ownerId){
  34. return repo.FindBy(ownerId);
  35. }
  36. }

The problem is that the in-memory repository is using the same object references all the time. When an object is passed in as an argument, it's actually a copy of the reference. However if the repository was accessing a database it would most likely return different instances (references). A better implementation of the repository would be this one:

  1. public class InMemoryRepo : JobRepository {
  2. readonly List jobs = new List();
  3. public void Add(Job job)
  4. {
  5. /* clone: the object added to the repo
  6.   is different to the given one */
  7. jobs.Add(job.Clone());
  8. }
  9. public void Update(Job job)
  10. {
  11. /* find the given job,
  12.   remove it from the collection and
  13.   add a clone of the given one */
  14. }
  15. public List FindBy(string ownerId)
  16. {
  17. return jobs.Where(job => job.IsOwnedBy(ownerId)).ToList();
  18. }
  19. }

Conclusion: the behavior of the fake should be the same than the original when it comes to handling object references.

Enjoyed reading this post?
Subscribe to the RSS feed and have all new posts delivered straight to you.
  • http://www.carlosble.com/ Carlos Ble

    Alternatives to “Clone” are:
    – Serializing and deserializing the object to JSON
    – Binary serialization
    – Implement a copy constructor
    – Implement known interfaces like ICloneable

  • Pawel Duda

    Hey Carlos! Uncle Bob was talking about exactly the same problem in his latest “Case Study” video (episode 4). The first 7 minutes of the episode describe how he refactored the project to make the fake behave like the real implementation (cloning, id assignment). If you are interested, here’s the code: https://github.com/cleancoders/CleanCodeCaseStudy/blob/master/test/cleancoderscom/tests/doubles/GatewayUtilities.java

  • Pablo Albizu

    Thanks a lot for your post Carlos!!

  • http://www.carlosble.com/ Carlos Ble

    Thank you Pawel! This video was in my queue. Now I am changing the priority to watch it very soon! Cheers mate

  • http://www.carlosble.com/ Carlos Ble

    You’re welcome mate 😉