Working as a consultant with lots of different teams helping to promote engineering practices I often hear ‘reasons’ as to why teams and developers don’t or can’t write automated tests. Most developers and teams want to do things that will ultimately make their lives easier, but sometimes hurdles and issues seem too big to overcome, and so they stop trying! One conversation with a particular developer a while back went along the lines of “the tests slow me down”. This intrigued me as personally I find that TDD speeds me up - so I asked a few more questions and paired with him to see his approach. Long story short I found that when building this developer changes his mind and his constructor injected dependencies a lot, and without auto mocking he was changing his mocks and construction more than his code.
Everyone’s approach is different – that’s what makes the world so great right – so when I listened and understood the pain I was able to offer a solution. I think all too often we just say “do this” or “do that” without looking at the reasons people may be struggling…
I will try and demonstrate the change in approach to auto mocking that helped in this case using a massively fictitious (and equally tragic) example.
We have an article service that returns articles based on a string Identifier (presumably to display somewhere). The first test written using NUnit and Moq to read the article looks like so:
1 [Test]
2 public void returns_read_article()
3 {
4 var repository = new Mock<IArticleRepository>();
5 repository.Setup(r => r.Get(It.IsAny<string>())).Returns(new Article());
6
7 var service = new ArticleService(repository.Object);
8
9 var article = service.Get(Guid.NewGuid().ToString());
10 Assert.IsNotNull(article);
11 }
And the simplest ‘production’ code looks like:
1 public class ArticleService
2 {
3 private readonly IArticleRepository _repository;
4
5 public ArticleService(IArticleRepository repository)
6 {
7 _repository = repository;
8 }
9
10 public Article Get(string articleId)
11 {
12 return _repository.Get(articleId);
13 }
14 }
I told you it was a tragic example right!
So the issue this developer had was that whenever he changed his mind and needed to introduce another dependency, all of his tests need to be changed (OK in this case it’s one test, but if you have 20 tests that’s a bit more work). Let’s simulate the change with another tragic example – let’s say we have been asked to change the system to record the popularity of articles – we decide that the easiest way is to change our ArticleService to use another dependency to record the request of read of the article.
Obviously as soon as we add another dependency the test code will need to be change too. So what are our choices – 1. give up 2. Put up with it and re-factor all of the tests 3. try and find a way to focus our use of mocked dependencies. 1 and 2 have already been tried! So lets try 3!
How about we automatically create Moq’s for the service – like when we use a DI container, just injecting Moq’s that we can control. Most test/mocking frameworks already have a capability – they aren’t too tricky to build, but as we are using Moq how about we NuGet install and use AutoMoq.
1 [Test]
2 public void returns_read_article()
3 {
4 AutoMoqer moqer = new AutoMoqer();
5 moqer.GetMock<IArticleRepository>().Setup(r => r.Get(It.IsAny<string>())).Returns(new Article());
6
7 var service = moqer.Create<ArticleService>();
8
9 var article = service.Get(Guid.NewGuid().ToString());
10 Assert.IsNotNull(article);
11 }
Simple changes – we new up an AutoMoqer tell it to use a Moq with the same Setup for our repository then create our service using the Moqer – tests run green. Now we can add our test for the new feature:
1 [Test]
2 public void records_article_read()
3 {
4 AutoMoqer moqer = new AutoMoqer();
5 moqer.GetMock<IArticlePopularityManager>().Setup(r => r.RecordRead(It.IsAny<string>())).Verifiable();
6
7 var service = moqer.Create<ArticleService>();
8
9 service.Get(Guid.NewGuid().ToString());
10 moqer.GetMock<IArticlePopularityManager>().Verify();
11 }
This time we only deal with our new popularity manager (I know tragic!) – obviously the test fails – so we step by step change the implementation until we get green tests and we end up with:
1 public class ArticleService
2 {
3 private readonly IArticleRepository _repository;
4 private readonly IArticlePopularityManager _articlePopularityManager;
5
6 public ArticleService(IArticleRepository repository,
7 IArticlePopularityManager articlePopularityManager)
8 {
9 _repository = repository;
10 _articlePopularityManager = articlePopularityManager;
11 }
12
13 public Article Get(string articleId)
14 {
15 _articlePopularityManager.RecordRead(articleId);
16 return _repository.Get(articleId);
17 }
18 }
19
Our existing test (apart from our re-factor to auto mock) was untouched and still runs green – so we can now amend dependencies to our hearts content only affecting tests relying on those dependencies. So our developer is happy that he can write tests without this issue slowing him down.
Really simple. Before you start thinking this is still a bit kek with all the duplication ‘n stuff – go and have a look at the AutoMoqTestFixture in the AutoMoq.Helpers namespace, then re-factor again!