How to Convince Your Business of Sponsoring a Large Scale Refactoring

Whenever I present or suggest a good practice to dev teams, I often get the same remark. Here is how it goes :

  • That’s a great idea and we would love to do this, but our code is in such a mess that we cannot !

  • Maybe you should start doing more refactoring then !

  • We would like to, but we don’t have the time. We are fire fighting all the time.

It’s a bit like the old adage of the lumberjack that is too busy to cut wood to sharpen his axe… The sad part here, is that most of the time, developers know they would be a lot faster if they could clean up their code. Unfortunately, they are usually not given the time.

How do we end up in this silly situation ?

Drawing of a '5 whys' mind map explaining why it is difficult to get sponsorship for a large scale refactoring

Only developers see the bad code

As I’ve already been joking about, code is invisible. Mess in the code even more so, especially to people who don’t code. The code could look like that and no one would notice.

Inside of a kitchen from someone suffering from Diogenes syndrome

By [A Tourist](http://commons.wikimedia.org/w/index.php?title=User:Un_Touriste&action=edit&redlink=1) - Private photography [CC BY-SA 3.0](https://creativecommons.org/licenses/by-sa/3.0), [Link](https://commons.wikimedia.org/w/index.php?curid=15988115)

If someone put his own office in that state, he would get fired, but not for the source code. The good side is that we, developers, are safe, we can continue to wreak chaos without fear ! That’s pretty weird when we think that this is what we ship to customers …

💡 Is Diogenes syndrome for source code a recognized pathology ?

Business might also not see bad code because that’s the only thing they’re used to ! Maybe they’ve always been working in dysfunctional organizations that systematically create crappy code. Slow teams, late deliveries and fire fighting might be business as usual for them. From this point of view, trying to improve code is a pure waste of time and energy. The same goes for large scale refactorings.

The worse part of all this is that if devs don’t have the time to keep their code clean, it will only get worse. This will reinforce the view that software delivery is slow and that there is nothing to do about it !

Business has been burnt in the past !

Bad experiences are another reason why business is unwilling to sponsor refactoring. Did someone sell them an unrealistic productivity boost that turned in a never-ending tunnel project ? Badly managed large scale refactorings deliver late, create no value, and a lot of bugs. At one company I worked for, business gave devs 1 full year (!) to clean up the code … We took 2 !! Meanwhile, the CEO had to dilute the stocks a bit to keep the boat afloat ! I’d think twice before giving this kind of mandate myself.

Performing a large scale refactoring is not easy, and involves specific skills. These skills are about refactoring in baby steps, alongside feature delivery.

Usually, people acquire these skills through hard won experience … Unfortunately for us, our industry is not very nice to experienced engineers … It’s a lot easier to hire a fresh grad who knows the latest javascript framework than a 2 decades engineer. (Who, BTW, could learn this framework in 2 weeks …) It’s also a lot harder for the junior developer to succeed in negotiating a refactoring.

Again the twist of fate is that junior engineers are a lot more likely to start a submarine latest-framework.js rewrite supposed to solve all maintenance issues … which will only make things worse.

Overestimate, only as last resort

A quick fix is to systematically overestimate to get enough time to refactor. As any other ‘submarine’ initiative, I would recommend it only in last resort, after you’ve tried every other possible technique … and just before you quit.

Hiding things to the business people kills trust and hides problems. Trust and collaboration is what you need to get the business to sponsor large scale refactorings ! Plus, if ever you mess up (as submarine initiative often do) you’ll be the only one to blame …

That said, ‘overestimating’ so that you can write clean code is ok. It’s not overestimating, it’s estimating to do a good job.

💡 We should never ask the permission to do a good job. (Doc Norton)

To be continued

You might wonder what these other techniques are ! That’s exactly what I’ll go through with the next posts. This was the first one in a series about how to get sponsorship for a large scale refactoring. The series will cover topics like :

  1. How to convince your business of sponsoring a large scale refactoring
  2. Why we need Badass developers to perform large scale refactorings
  3. 5 mistakes badass developers never do
  4. Principles That Will Make You Become a Badass Developer
  5. Incremental Software Development for Large Scale Refactoring
  6. Incremental Software Development Strategies for Large Scale Refactoring #1 : Constant Merciless Refactoring
  7. Incremental Software Development Strategies for Large Scale Refactoring #2 : Baby Steps
  8. Incremental Software Development Strategies for Large Scale Refactoring #3 : Manage it !
  9. Incremental Software Development Strategies for Large Scale Refactoring #4 : a Pattern Language
  10. Presenting a large scale refactoring as a business opportunity
  11. 5 Effective warning signals that will get you sponsorship for a large scale refactoring
  12. Nothing convinces business people like money

How to Avoid Unnecessary Meetings (a Takeaway From Devoxx France 2018)

I had the chance to attend Devoxx France this year in Paris. Here is the most important lesson I learned :

How to avoid unnecessary meetings with asynchronous decision making

Bertrand Delacretaz, a member of the Apache foundation. He gave a great talk about how the open source community handles decision taking. Open source developers are often all over the world, often in different timezones. Meetings are not an option for them. Still, they manage to make great decisions !

Drawing of a decision hammer

Even if you don’t work remotely, avoiding unnecessary meetings is always a great thing !

  1. You’ll have more time to do productive and interesting stuff
  2. You’ll avoid interruptions and be even more productive
  3. If you are an introvert, it’s going to be easier to contribute to the decision
  4. As people have more time to think through the decision, the result is usually better

For a full walkthrough, I encourage you to watch the talk in full length. If you don’t speak french, an english version is available here. Finally, slides are also available in french and english.

💡 Even if you don’t work remotely, avoiding unnecessary meetings is always a great thing !

Crash course

For the hasty folks among you, here is a summary. The decision making follows 4 stages :

  1. Open discussion and brainstorming. People discuss openly and suggest ideas in a free form manner.
  2. Emergence of options. After enough discussion, a few options will start to make more sense than others.
  3. Coming to a consensus. Someone will draft a formal proposal. People will discuss and amend this proposal until they reach consensus. Consensus is not unanimity !
  4. Decision. After consensus, the benevolent decision owner validates the decision once and for all.

Until the decision is taken, the process can move forward but also backward.

Tooling

We need only two tools to make this possible :

  1. For discussion, brainstorming and emergence of options, use a very open and chatty tool. The speaker called this a “shared asynchronous communication channel”. This can be an online chat, a mailing list or Github issues (ex). It could even be a real life whiteboard if you all had access to it.
  2. From drafting the proposal to the end, prefer a structured and chronological tool. The speaker suggests using a “shared case management tool”. Draft the proposal in this tool, and use comments to log the latest steps of the decision taking. He had examples using Jira issues (ex) or Github pull requests (ex). To confirm the decision, close the case. The tool will record which version of the decision was exactly taken.

Architecture Decision Record

Drawing of an Architecture Decision Record which work great with asynchronous decision making

ADR is the practice of documenting architecture decisions. It makes sure we remember why we took a decision. This can be very useful to know how to deal with the existing software. A widespread practice for ADRs is to use simple text files in git. There are even tools for that. This looks like a perfect fit for decision making using git pull requests ! I’ll write a post about that when I get the chance to try.

💡 Git pull requests based asynchronous decision making is a perfect fit for Architecture Decision Records.

Currently experimenting

I am currently trying this whole decision making technique at work. We are still in the brainstorming phase. We are using our internal chat app for that. Options are starting to emerge, but we did not move to the consensus part yet. I’ll write a return on experience post when we reach the end.

A Coding Dojo Exercises Plan Towards Refactoring Legacy Code

My current job at work is technical coach. I’m available for teams that need help to adopt incremental coding practices.

Problems with refactoring legacy code

A few months ago, a team which was struggling with a lot of legacy code asked for help. As you might know if you read my blog, I’m a big fan of Test Driven Development (TDD) because it has made my life as a developer so much more easy. I’m so used to TDD now, that even if I don’t have tests yet (as is the case when refactoring legacy code), TDD helps me :

  • To stick to baby steps which are a lot less likely to fail than larges changes.
  • Write testable code. I know what testable code looks like, and when refactoring, I’ll try to change it towards that.

That’s why we started to run regular, all team, coding dojo randoris. It was nice for the team dynamics, and the people where learning a lot of technical skills. I also got the feedback that they where not able to apply this directly on their day to day job though. After a bit more discussion, I understood that they did not know where this was going, what to expect, and when !

💡 Test Driven Development also teaches you what testable code looks like.

The coding dojo exercices

It turned out that a coding dojo exercises plan was enough to answer their questions. This is what it looks like.

Drawing

An illustrated Coding Dojo Exercises plan leading to the mastery of Legacy Code Refactoring

Mind Map

Here is another, more concrete, version, with sample names of katas we can find online.

An mind map of Coding Dojo Exercises plan leading to the mastery of Legacy Code Refactoring

Text

It starts with simple greenfield katas :

It goes on to intermediate katas, where we can use TDD to do design :

From then on, it’s possible to tackle advanced katas and styles :

All this opens the gate to legacy code refactoring katas :

At that point, the team can mob to refactor production code :

  • Real life, static analysis issue, mob programming session
  • Real life, code smell, mob programming session
  • Real life, larger mob Refactoring

What changed in practice ?

We wanted to split the teamwork and the coding dojos exercises. The team is now doing mob programming sessions on their usual stories twice a week (I’ll blog about that someday). But also doing regular coding dojos exercises in pairs.

Even if they did not go through all the TDD katas yet, mobbing on real stories helps the team to take on legacy code.

Given enough eyeballs, all bugs are shallow. Linus’s Law

Working in pairs on the code katas allows them to be more engaged in the exercises. In the end, it brings faster learning.

💡 A mix of Coding Dojos in pairs and Mob Programming sessions is a good way to teach TDD in a Legacy Code context.

When Is Testing Using Mocks Still a Good Idea ?

In the previous 7 articles of this series, I’ve tried my best get rid of mocks. I’m pretty sure that using these techniques will get you a long way out of mock hell. Excessive mocking leads to unmaintainable tests. Unmaintainable tests lead to low coverage. Low coverage ultimately leads to legacy code. If you haven’t already, I encourage you to start reading from the beginning.

One question remains though : Is it realistic to get rid of all mocks ? An even better question would be : Are mocks always bad ? Are there situations when mocking is the best choice ?

When mocking still makes sense

Let’s to through a few examples.

Testing a generic wrapper

A few years ago, I had to write a service for an enterprise system. As any service, I had to ensure that it was returning nice errors. We decided to capture and wrap all errors from a few ‘gate’ points in the code. We built a generic wrapper that did only delegation plus exception wrapping. In this case, it made a lot more sense to test this with a mocking framework.

1
2
3
4
5
6
7
8
9
10
11
context ServiceErrorWrapper do

 specify 'converts all kinds of exceptions' do
   failing_object = object_double("Failing object")
   allow(failing_object).to receive(:long_computation).and_raise(Exception.new("Something terrible happened"))

   expect{ ServiceErrorWrapper.new(failing_object).long_computation }.to raise_error(ServiceError).with_message("Something terrible happened")
 end

 # ...
end

Not only did we reuse the wrapper many times in my service. We also ended up using it in other services as well !

Injecting a hand written in-memory fake

As you might have noticed, in the previous article, I recommended to use an in-memory fake instead of mocks. By nature, an in-memory fake is a kind of mock. Even if it is not defined by a mocking framework. (I actually think that by making mocking so easy, mocking frameworks often do more harm than good.)

💡 By making mocking so easy, mocking frameworks often do more harm than good.

Still, I used const_stub(...) to inject the in-memory fake.

1
2
3
4
5
config.before(:each) do  

  stub_const("TwitterClient::Client", FakeTwitterClient.new)  

end  

I did this for 2 reasons :

  • Production code can continue to use a straightforward constant
  • I don’t risk forgetting to remove the mock at the end of its lifecycle, the framework does this for me
  • As I’m injecting the same fake for all tests, there is not much risk of test conflict (for the moment)

Testing a cache

The “raison d’être” of a cache is to avoid doing something twice. It should also return the same results as if it was not there. This is by nature almost impossible to test with state based assertions. Mock frameworks are great for this situation though. Here is an example :

1
2
3
4
5
6
7
8
context "UsersController" do
 it 'caches users' do
   expect(User).to receive(:load).once.and_return(User.new(name: "Joe"))

   controller.login('Joe', 'secret')
   controller.login('Joe', 'secret')
 end
end

The assertion could not be more explicit, we are checking that the expensive load was only done once.

Legacy code

Michael C.Feathers explains that testing using mocks is a key practice in "Working Effectively with Legacy Code"

In Working Effectively with Legacy Code Michael Feathers explains how to exploit “seams” in the code to put it under test. Mocking is straightforward way to inject behavior through a seam.

Mocking is a pretty good starting point but we need to be careful and keep a few things in mind. Legacy or not, we must not forget that too many mocks will make tests unmaintainable !

  • It’s a good idea to refer to a target design or architecture blueprint to know where to inject mocks. (I’ll write a post about this one day). This increases the chances to replace them with an in-memory fake later down the road.
  • Plan to replace the mocks with a better design as soon as possible.

It depends …

As with anything in software, there is no absolute rule about mocking. Even if I prefer not to 99% of the time, there are situation when testing using mocks is the thing to do. Knowing the risks, it’s up to you to decide !

If using a mock, prefer spy / proxies

Spies and proxies make testing using mocks less intrusive

As I explained in previous posts, mocks duplicate behavior. If we could use mocks without duplicating behavior, they would do less harm.

It turns out there is a flavor of mocks for that : spies and overlooked proxies. Proxies do the real thing but also record the calls and return values. It’s as non-intrusive as mocks can be.

💡 Proxy mocks are as unintrusive as mocks can be.

For example, here is how our cache test would look like using a proxy :

1
2
3
4
5
6
7
8
9
10
context "UsersController" do
 it 'caches users' do
   allow(User).to receive(:load).and_call_original

   controller.login('Joe', 'secret')
   controller.login('Joe', 'secret')

   expect(User).to have_received(:load).once
 end
end

It’s more verbose, but simpler. Most mock frameworks provide some form of spy or proxies. A few years ago, I also wrote rspecproxies, a wrapper on top of rspec to make this easier.

This is the end

This was the 8th and last post in a series about how to avoid mocks. Before closing here is a list of other references about the topic.

Get Rid of Mock Maintenance With Full Fledged In-memory Fakes

Last week’s post was about how hexagonal architecture results in fast, mock-free tests around your core domain. Unfortunately, that does not remove all mocks, yet it groups them in the same, less critical, zone. In last week’s code sample, this was the controller. I concluded that at least, this was easier to manage. Let’s see how.

Hand written 'In-memory fake' with memory replaced by a RAM board

This is the 7th post in a series about avoiding mocks. If you haven’t, you might start from the beginning.

Mock concentration

Let’s get back to the last post’s code sample. As a reminder, it’s a very basic TODO app built on Rails. I extracted the domain part, the tasks, in a core domain area. This allowed to push all mocks out of this section. A consequence though, is that all mocks gathered in the controller test. Here is the controller code :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
require 'core/task'
require 'infrastructure/task_repo'

class TasksController < ApplicationController
 before_action :set_task, only: [:show, :edit, :update, :destroy]

 # GET /tasks
 def index
   @tasks = Infrastructure::TaskRepo.all
 end

 # GET /tasks/1
 def show
 end

 # GET /tasks/new
 def new
   @task = Core::Task.new
 end

 # GET /tasks/1/edit
 def edit
 end

 # POST /tasks
 def create
   begin
     @task = Core::Task.new(task_params)
     Infrastructure::TaskRepo.save(@task)

     redirect_to task_url(@task.db_id), notice: 'Task was successfully created.'

   rescue ArgumentError
     render :new
   end
 end

 # PATCH/PUT /tasks/1
 def update
   begin
     @task.update(task_params)
     Infrastructure::TaskRepo.save(@task)

     redirect_to task_url(@task.db_id), notice: 'Task was successfully updated.'

   rescue ArgumentError
     render :edit
   end
 end

 # DELETE /tasks/1
 def destroy
   Infrastructure::TaskRepo.delete(@task)
   redirect_to tasks_url, notice: 'Task was successfully destroyed.'
 end

 private
   def set_task
     @task = Infrastructure::TaskRepo.load(params[:id])
     @task.notify_when_done do |task|
       TwitterClient::Client.update(task.description)
     end
   end

   # Never trust parameters from the scary internet, only allow the white list through.
   def task_params
     params.permit(:description, :done)
   end
end

The controller is now dealing both with the Twitter connection and the database. This is visible in the controller test :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
require 'rails_helper'

RSpec.describe TasksController, type: :controller do

 before :each do
   allow(TwitterClient::Client).to receive(:update)
 end

  # ...

 describe "PUT #update" do
   context "with valid params" do
     let(:new_attributes) {
       {done: true}
     }

     it "updates the requested task" do
       task = Task.create! valid_attributes
       put :update, params: new_attributes.merge(id: task.to_param)
       task.reload
       expect(task).to be_done
     end

     it "tweets about completed tasks" do
       task = Task.create! valid_attributes

       expect(TwitterClient::Client).to receive(:update).with(task.description)

       put :update, params: {id: task.to_param, done: true}
     end

     it "redirects to the task" do
       task = Task.create! valid_attributes
       put :update, params: valid_attributes.merge(id: task.to_param)
       expect(response).to redirect_to(task_url(task.id))
     end
   end

   # ... 

  end
end

We need to stub out the twitter API for most tests. We are also still using a mock to verify that the tweet is sent. Finally, as we can see from the test execution times, we are still using the database in some tests.

Screen capture of the tests execution time

If the project grew large this would become an issue. Sadly, mocking is often the fix people jump on …

💡 Mocking is the unfortunate quick fix to slow tests.

From a mocking point of view, our current controller test can seem worse than before ! There’s something pretty effective we can do though !

In memory fakes

Instead of stubbing and mocking in every test, let’s write a full fledged in-memory fake that does the job we need. We could then install it once and for all, and forget about it !

Actually, this is nothing new. This is exactly what Rails provides out of the box with ActionMailer::Base.delivery_method = :test.

Here’s how we could do the same thing for our Twitter Client.

spec/rails_helper.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class FakeTwitterClient
 def initialize
   @tweets = []
 end

 attr_accessor :tweets

 def update(message)
   @tweets.push(message)
 end
end

RSpec.configure do |config|

  # ...
 config.before(:each) do
   stub_const("TwitterClient::Client", FakeTwitterClient.new)
 end
end
spec/controllers/tasks_controller_spec.rb
1
2
3
4
5
6
7
it "tweets about completed tasks" do
 task = Task.create! valid_attributes

 put :update, params: {id: task.to_param, done: true}

 expect(TwitterClient::Client.tweets).to include(task.description)
end

Simple isn’t it ?

Wait a sec …

There’s a catch though … How do we make sure that this fake is behaving the same way as the real thing ?

Let’s run the same tests on both ! We could mimic the twitter API in our fake, but that might not be a great idea. Do you remember the moto “Always wrap your 3rd parties” ? It takes all its meaning here, for 2 reasons.

The first is to make faking easier. We can build a minimal wrapper API that is just enough for our use. By keeping this interface small, we’ll make it a lot easier to fake.

The second reason is that we can write real integration tests on the 3rd party through this wrapper. They’d look like ordinary unit tests, except that they’d end up calling the real 3rd party in a sandbox. They are usually pretty slow, but as 3rd parties don’t change everyday, that’s ok. We can ensure up-front that integration will go well. As a bonus, we can be very fast to detect and contain changes to online services. (I’m looking at you Scrappers!)

Here is what it would look like for our Twitter client :

lib/infrastructure/twitter_client.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class FakeTwitterClient
 def initialize
   @tweets = []
 end

 attr_accessor :tweets

 def tweet(message)
   @tweets.push(message)
 end

 def search_tweets(text)
   @tweets.select {|tweet| tweet.include?(text) }
 end
end

class RealTwitterClient
 def initialize(&block)
   @client = Twitter::REST::Client.new(&block)
 end

 def tweet(message)
   @client.update(message)
 end

 def search_tweets(text)
   @client.search("from:test_user #{text}")
 end
end

As you can see, we renamed update to tweet in the wrapper. We’d have to update the calls accordingly. Let’s look at the tests.

spec/lib/Infrastructure/twitter_client_spec.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
require 'rails_helper'
require 'infrastructure/twitter_client'
require 'securerandom'

RSpec.shared_examples "a twitter client" do |new_client_instance|
 let(:client) { new_client_instance }
 it "sends tweets" do
   token = SecureRandom.uuid
   message = "Philippe was here #{token}"
   client.tweet(message)

   expect(client.search_tweets(token)).to include(message)
 end
end

context FakeTwitterClient do
 it_behaves_like "a twitter client", FakeTwitterClient.new
end

context RealTwitterClient, integration: true, speed: :slow do
 it_behaves_like "a twitter client", (RealTwitterClient.new do |config|
   config.consumer_key        = "TEST_CONSUMER_KEY"
   config.consumer_secret     = "TEST_CONSUMER_SECRET"
   config.access_token        = "TEST_ACCESS_TOKEN"
   config.access_token_secret = "TEST_ACCESS_SECRET"
 end)
end

We had to add a search method to our interface for the sake of testing. This should remain “For testing only”. We’d also adapt the controller test to use this search_tweets method.

Let’s look at where we stand now. We’re injecting each mock only once. Tests are fast yet straightforward, almost as if they were testing the real thing. Doing so, we’ve split our system in cohesive parts and we’ve wrapped our 3rd parties. We’ve actually done a lot more than removing mocks ! Mocking really is a design smell.

💡 Merciless mock hunting will improve the design of your system !

Last word about implementation

Sometimes, this 3rd party wrapper can become pretty complicated. Try to reuse as much of it as possible between the real and the fake. For example, an ORM, like ActiveRecord for example, is a wrapper around the database. Reimplementing a fake ORM would be real challenge. We’re far better plugin it on top of SQLite instead !

References

Smart people have already spoken and written about this subject. If you want to learn more, I recommend that you have a look at Aslak Hellesøy’s Testable Architecture talk. James Shore, the author of The Art of Agile Development, also wrote a pattern language called Testing Without Mock.

Next week

This was the 7th blog post in a series about how to avoid mocks. Hopefully, I’m reaching the end ! Next week’s post should be the last in series, and deal with a few remaining points. What to do when you really need a mock ? What about mocking and legacy code ?

Avoid Mocks and Test Your Core Domain Faster With Hexagonal Architecture

As I’ve written in my last few posts, we can get a long way to avoid mocks with small scale coding best practices. Unfortunately, when systems reach a certain size, we need something at architecture scale.

This is the 6th post of a series about avoiding mocks. If you haven’t, you can start by the beginning.

A drawing of a hexagon-shaped building

Why do we end up with mocks in large systems ?

A few years ago, I joined a team working in a legacy system. We wanted to apply TDD and refactoring. As expected, adding tests legacy code proved a real challenge. With a lot of effort we could manage to add a few. Unfortunately, this did not seem to have any positive effect on our maintainability ! The tests we were writing all involved a lot of mocking. The system was such a large mass of spaghetti code that there was no clear place to mock. We were actually mocking where it seemed the easiest on a test by test basis. We were making progress at small scale, but the big picture was not improving at all !

Large systems are beasts with many faces. They  involve a lot of IOs. They write and read data from the disk and databases. They call 3rd parties and remote services.

As we test these large systems, we’ll need to stub out these IOs. Even if the tests are fast enough, we usually don’t want to call external services for real. Most of the time though, tests are slow. That’s 2 reasons why end up adding some mocks.

Here comes the nasty part. These large systems are so complex that we, developers, don’t have the full picture. When we test, we tend to mock at different places, depending on our knowledge. This is bad for maintenance. Mocks duplicate production code behavior. When many different mocks are in place to isolate an external dependency, we end up with ‘n’ versions of the code. That’s a nightmare to refactor !

💡 When many different mocks are in place to isolate an external dependency, we end up with ‘n’ versions of the code !

Hexagonal architecture to the rescue

Alistair Cockburn coined the term. The idea is pretty simple :  isolate a piece of code from all dependencies. This is particularly useful for the core functional areas. With this in place, it becomes straightforward (and fast) to test the core domain logic.

To main techniques to isolate a piece of code from any dependency are :

It’s also possible to split a system in many ‘hexagons’ and glue them together with adapters at startup. If you want to learn more on this style of architecture, have a look into the Domain Driven Design lore. This community has been building systems this way for years now.

Enough talk, show me the code !

This post was the occasion to try to inject a Hexagonal Architecture and a dash of DDD in a Rails application. There’s one caveat though : DDD shines on complex systems. Unfortunately, large and complex systems make very poor didactic examples. The following code highlights the gains about mocking. We would not use DDD for such a small app in real life.

The starting point

I chose a simple TODO app. I started by generating a scaffold for a Task with a description and a done/not-done status. As third party interaction, completing a task sends an automatic tweet. Here is the only specific code I wrote on top of the Rails scaffold :

app/models/task.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Task < ApplicationRecord
  include ActiveModel::Dirty

  validates :description, presence: true

  before_save :tweet_if_done

  private
  def tweet_if_done
    if done_changed?
      TwitterClient::Client.update(self.description)
    end
  end
end

Thanks Jason Charnes for the change attribute technique.

spec/models/task_spec.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
require 'rails_helper'

RSpec.describe Task, type: :model do

  it "is valid with all attributes set" do
    expect(Task.create(description: "Finish presentation", done: false)).to be_valid
  end

  it "requires a description" do
    expect(Task.create(description: nil, done: false)).to be_invalid
    expect(Task.create(description: "", done: false)).to be_invalid
  end

  it "tweets when a task is finished" do
    task = Task.create(description: "Wash the car", done: false)

    expect(TwitterClient::Client).to receive(:update).with("Wash the car")

    task.done = true
    task.save
  end
end

This is pretty simple and to the point !

5 years later

Now let’s imagine that the app grew to tens of thousands of lines. We added a lot of features to the app, which transformed the TODO domain into a very complex thing. Now suppose that, for the sake of maintenance, we want to isolate the domain logic into its own hexagon. Unlike traditional Rails ActiveRecords, we want to make it independent from the database. We also want it to be independent from the Twitter API.

Here is what the code might look like.

lib/core/task.rb

First, we have a core task class, independent from anything else. The Core module is our hexagon.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
module Core
  class Task

    attr_reader :description
    attr_accessor :db_id

    def initialize(attributes = {})
      @description= "What do you need to do ?"
      @done = false
      @done_subscribers = []

      self.update(attributes)
    end

    def done?
      @done
    end

    def mark_as_done
      @done = true
      @done_subscribers.each {|proc| proc.call(self) }
    end

    def update(attributes={})
      self.description= attributes[:description] unless attributes[:description].nil?
      self.mark_as_done if attributes[:done]
    end

    def notify_when_done(&proc)
      @done_subscribers.push(proc)
    end

    def description=(desc)
      raise ArgumentError.new("Task description cannot be blank") if desc.blank?

      @description = desc
    end
  end
end

As we can see, it contains only domain logic and nothing else.

# spec/lib/core/task_spec.rb

Here is the corresponding test, fast, mock-free and independent from the database and any external system.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
require 'rails_helper'
require 'core/task'

context 'Task' do

  let(:task) { Core::Task.new}

  specify 'is not done by default' do
    expect(task).not_to be_done
  end

  specify 'comes with a default description' do
    expect(task.description).not_to be_blank
  end

  specify 'it can be initialized from a hash' do
    task = Core::Task.new(description: "Old description", done: true)

    expect(task.description).to eq("Old description")
    expect(task).to be_done
  end

  specify 'can have a custom description' do
    task.description= "Clean up the house"
    expect(task.description).to eq("Clean up the house")
  end

  specify 'forbids empty descriptions' do
    expect{task.description = nil }.to raise_error(ArgumentError)
    expect{task.description = "" }.to raise_error(ArgumentError)
  end

  specify 'can be done' do
    task.mark_as_done
    expect(task).to be_done
  end

  specify 'publishes when done' do
    done_task = nil
    task.notify_when_done {|t| done_task = t}

    task.mark_as_done

    expect(done_task).to be(task)
  end

  specify 'can be updated with a hash' do
    task.update(description: "New description", done: true)

    expect(task.description).to eq("New description")
    expect(task).to be_done
  end

  specify 'has no DB id by default' do
    expect(task.db_id).to be_nil
  end
end
# lib/infrastructure/task_repo.rb

To read and save with the database, we now go through an adapter. This is not considered to be part of our core domain.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
module Infrastructure
  class TaskRepo

    def self.all
      Task.all.map do |db_task|
        from_db(db_task)
      end
    end

    def self.load(db_id)
      from_db(Task.find(db_id))
    end

    def self.save(task)
      if task.db_id.nil?
        db_task = Task.create!(to_db_attributes(task))
        task.db_id = db_task.id
      else
        db_task = Task.find(task.db_id)
        db_task.update!(to_db_attributes(task))
      end
      task
    end

    def self.delete(task)
      unless task.db_id.nil?
        db_task = Task.find(task.db_id)
        db_task.destroy!
        task.db_id = nil
      end
    end

    private

    def self.to_db_attributes(task)
      {description: task.description, done: task.done?}
    end

    def self.from_db(db_task)
      result = Core::Task.new
      result.db_id = db_task.id
      result.description = db_task.description
      result.mark_as_done if db_task.done?
      result
    end

  end
end
# app/controllers/tasks_controller.rb

Finally, all the pieces interact together in the controller. This controller basically does what the previous version was, it’s just using different classes. Obviously, we’ll need to adapt the views and the tests.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
require 'core/task'
require 'infrastructure/task_repo'

class TasksController < ApplicationController
  before_action :set_task, only: [:show, :edit, :update, :destroy]

  # GET /tasks
  def index
    @tasks = Infrastructure::TaskRepo.all
  end

  # GET /tasks/1
  def show
  end

  # GET /tasks/new
  def new
    @task = Core::Task.new
  end

  # GET /tasks/1/edit
  def edit
  end

  # POST /tasks
  def create
    begin
      @task = Core::Task.new(task_params)
      Infrastructure::TaskRepo.save(@task)

      redirect_to task_url(@task.db_id), notice: 'Task was successfully created.'

    rescue ArgumentError
      render :new
    end
  end

  # PATCH/PUT /tasks/1
  def update
    begin
      @task.update(task_params)
      Infrastructure::TaskRepo.save(@task)

      redirect_to task_url(@task.db_id), notice: 'Task was successfully updated.'

    rescue ArgumentError
      render :edit
    end
  end

  # DELETE /tasks/1
  def destroy
    Infrastructure::TaskRepo.delete(@task)
    redirect_to tasks_url, notice: 'Task was successfully destroyed.'
  end

  private
    def set_task
      @task = Infrastructure::TaskRepo.load(params[:id])
      @task.notify_when_done do |task|
        TwitterClient::Client.update(task.description)
      end
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def task_params
      params.permit(:description, :done)
    end
end

The main gain here is that our core domain, our most valuable asset is now easy to test without mocks. This means that we are able to write and execute fast tests for this area of the code. This puts us in a great position to increase our competitive advantage in our core business !

💡 By keeping your tests around your core domain fast, Hexagonal Architecture increases your competitive advantage.

As you can see, we are now wiring everything together at the controller level. We could later build a facade to isolate the controller from the inside of our domain. A presenter might do, but it seemed over-engineered, even in this made up example. (I’ll post something about that some day)

Next post

As we can deduce from the controller code above, we still have to use fakes or mocks when testing the controller. The good thing though is that this is now more local which already makes mocking less of an issue. If a mock is used in less tests, it’s easier to use the same mock everywhere ! This is a great opportunity for simplifying test setup, as we’ll see in the next post about in-memory fakes.

How Custom Assertion Matchers Will Keep Mocks Away

I cannot write a series about avoiding mocks without mentioning Custom Assertion Matchers. If you don’t know what custom assertions are, here is pseudo code that uses a custom assertion :

1
assert.that(actual, VerifiesMyCustomAssertion(withCustomProperties))

For more details, have a look at these examples for your preferred language : Java, Ruby or Javascript.

A drawing of a box of matches, branded 'Matchers' on top

That custom assertion matchers have an effect on mock usage might seem puzzling at first. Let me explain. Us, mere human developers, get lured into mocking when tests become too complicated. By keeping the tests simpler, Custom Assertion Matchers help use to avoid mocks. It’s a bit like why test data builders keep mocks at bay.

💡 We get lured into mocking when tests become too complicated

I already blogged about the benefits of Custom Assertion Matchers. Here I’m going to dive in their advantages against mocking.

This is the fifth post in a series about how to avoid mocks. If you haven’t yet, I recommend you to start from the beginning.

Why would we end up with mocks when we don’t have matchers ?

Let’s walkthrough a small story. Suppose we are building an e-commerce website. When someone passes an order, we want to notify the analytics service. Here is some very simple code for that.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class AnalyticsService

 def initialize
   @items = []
 end

 attr_reader :items

 def order_passed(customer, cart)
   cart.each do |item|
     @items.push(customer: customer, item: item)
   end
 end
end

class Order
 def initialize(customer, cart, analytics)
   @customer = customer
   @cart = cart
   @analytics = analytics
 end

 def pass
   # launch order processing and expedition  

   @analytics.order_passed(@customer, @cart)
 end

end

describe 'Order' do

 it "notifies analytics service about passed orders" do
   cart = ["Pasta","Tomatoes"]
   analytics = AnalyticsService.new
   order = Order.new("Philippe", cart, analytics)

   order.pass

   expect(analytics.items).to include(customer: "Philippe", item: "Pasta")
   expect(analytics.items).to include(customer: "Philippe", item: "Tomatoes")
 end
end

Let’s focus on the tests a bit. We first notice that the verification section is large and difficult to understand.  Looking in more details, it knows too much about the internals of AnalyticsService. We had to make the items accessor public just for the sake of testing. The test even knows how the items are stored in a list of hashes. If we were to refactor this representation, we would have to change the tests as well.

We could argue that responsibility-wise, our test should only focus on Order. It makes sense for the test to use a mock to verify that the Order calls AnalyticsService as expected. Let’s see what this would look like.

1
2
3
4
5
6
7
8
9
it "notifies analytics service about passed orders" do
 cart = ["Pasta","Tomatoes"]
 analytics = AnalyticsService.new
 order = Order.new("Philippe", cart, analytics)

 expect(analytics).to receive(:order_passed).with("Philippe", cart)

 order.pass
end

Sure, the test code is simpler. It’s also better according to good design principles. The only glitch is that we now have a mock in place with all the problems I described before.

This might not (yet) be a problem in our example but, for example, the mock ‘cuts’ the execution of the program. Suppose that someday, the Order starts expecting something from the AnalyticsService. We’d then need to ‘simulate’ the real behavior in our mock. This would make the test very hard to maintain.

Matchers to the rescue

Let’s see how a matcher could help us here. The idea is to improve on the first ‘state checking’ solution to make it better than the mock one. We’ll extract and isolate all the state checking code in a custom matcher. By factorizing the code in a single matcher, we’ll reduce duplication. The matcher remains too intimate with the object, but as it is now unique and well named, it’s less of a problem. Plus, as always with matchers, we improved readability.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
RSpec::Matchers.define :have_been_notified_of_order do |customer, cart|
 match do |analytics|
   cart.each do |item|
     return false unless analytics.items.include?(customer: customer, item: item)
   end
   true
 end
end

describe 'Order' do
 it "notifies analytics service about passed orders" do
   cart = ["Pasta","Tomatoes"]
   analytics = AnalyticsService.new
   order = Order.new("Philippe", cart, analytics)

   order.pass

   expect(analytics).to have_been_notified_of_order("Philippe", cart)
 end
end

Here is how we could summarize the pros and cons of each approach :

Assert state Mocks Matchers
👎 duplicated code 👎 duplicates the program behavior ❤️ customizable error messages|
👎 breaks encapsulation ❤️ more readable|
👎 intimacy with the asserted object|
❤️ factorizes the assertion code|

Design improvements

Depending on your situation, you might find further design improvements. In our example, a publish-subscribe pattern might do. A better design is likely to fix the encapsulation problem of the matcher. Here again, the custom assertion matchers will help. In most cases, it will be enough to change the implementation of the matchers only.

💡 Custom assertion matchers make refactoring easier by factorizing test assertions.

Summary of small-scale techniques

I’m done with small scale mock avoiding techniques. To summarize, the first thing to do is to push for more and more immutable value objects. Not only does it help us to avoid mocks, but it will also provides many benefits for production code. Practices like Test Data Builders and Custom Assertion Matchers simplify dealing with Immutable Value Objects in tests. They also help to keep tests small and clean, which is also a great thing against mocks.

Next post

In the following posts, I’ll look into architecture scale techniques to avoid mocks. I’ll start with Hexagonal architecture.

How to Use Test Data Builders to Avoid Mocks and Keep Your Tests Clear

We are sometimes tempted to use mocks to shortcut test data initialization. Unfortunately, excessive mocking makes tests difficult to maintain. As Uncle Bob explained, it’s a road that leads to giving up on tests.

Hopefully, Test Data Builders both shortcut test data setup and avoid mocks.

Drawing of a crate

This is the fourth post of a series about how to avoid mocks in automated tests. If you haven’t yet, I recommend you to start from the beginning.

The problem with test data initialization

Setting up the correct state for automated tests can be pretty verbose. This is especially true for software in complex domains or code with a lot of side effects.

The situation gets worse as tests need to setup similar but not exactly identical data. What I often see in code bases is a lot of test data setup duplication. For example, here are tests for a basic ticketing system.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
require 'rspec'
require 'date'

describe 'Ticket Tracking' do
  context "with test setup duplication" do

    it 'latest change date is the creation date when nothing changed' do
      creation_time = DateTime.new(2018,4,26,13,9,0)

      ticket = Ticket.new("Widget broken", "The widget is not loading when ...", "Philippe", creation_time)

      expect(ticket.latest_change).to be(creation_time)
    end

    it 'latest change date is the comment date when a comment is written' do
      ticket = Ticket.new("Widget broken", "The widget is not loading when ...", "Philippe", DateTime.new(2018, 4, 26, 13, 9, 0))
      comment_time = DateTime.new(2018, 4, 26, 13, 16, 0)

      ticket.add_comment(Comment.new("Should work now", "Dan", comment_time))

      expect(ticket.latest_change).to be(comment_time)
    end

    it 'latest change date is the comment date of the latest comment' do
      ticket = Ticket.new("Widget broken", "The widget is not loading when ...", "Philippe", DateTime.new(2018, 4, 26, 13, 9, 0))
      ticket.add_comment(Comment.new("Should work now", "Dan", DateTime.new(2018, 4, 26, 13, 16, 0)))
      comment_time = DateTime.new(2018, 4, 26, 18, 36, 0)

      ticket.add_comment(Comment.new("Should work now", "Dan", comment_time))

      expect(ticket.latest_change).to be(comment_time)
      end

    it 'latest change date is time of latest change if after comment' do
      creation_time = DateTime.new(2018, 4, 26, 13, 9, 0)
      ticket = Ticket.new("Widget broken", "The widget is not loading when ...", "Philippe", creation_time)
      ticket.add_comment(Comment.new("Should work now", "Dan", DateTime.new(2017, 4, 26, 13, 16, 0)))

      expect(ticket.latest_change).to be(creation_time)
    end
  end
end


## The code under test
##
class Ticket

  def initialize(title, description, reporter, creation_time)
    @updated_at = creation_time
    @comments = []
  end

  def latest_change
    ([@updated_at] + @comments.map(&:created_at)).max
  end

  def add_comment(comment)
    @comments.push(comment)
  end
end

class Comment
  attr_reader :created_at
  def initialize(message, author, time)
    @created_at = time
  end
end

It’s clear that there’s a huge amount of duplication in the tests data setups.

The straightforward fix against that is method extraction. This is the Object Mother pattern. Unfortunately, Object Mother breaks down under the number of variations. Every time you need a new change, you’ll add a parameter to the Object Mother method. Long story short, you’ll end up with code like that :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
describe 'Ticket Tracking' do
  context "with object mother" do

    it 'latest change date is the creation date when nothing changed' do
      creation_time = DateTime.new(2018, 4, 26, 13, 5, 0)
      ticket = create_ticket(creation_time, [])

      expect(ticket.latest_change).to be(creation_time)
    end

    it 'latest change date is the comment date when a comment is written' do
      comment_time = DateTime.new(2018, 4, 26, 13, 16, 0)
      ticket = create_ticket(DateTime.new(2018, 4, 26, 13, 9, 0), [comment_time])

      expect(ticket.latest_change).to be(comment_time)
    end

    it 'latest change date is the comment date of the latest comment' do
      comment_time = DateTime.new(2018, 4, 26, 18, 36, 0)
      ticket = create_ticket(DateTime.new(2018, 4, 26, 13, 9, 0),
                             [DateTime.new(2018, 4, 26, 13, 16, 0), comment_time])

      expect(ticket.latest_change).to be(comment_time)
    end

    it 'latest change date is time of latest change if after comment' do
      creation_time = DateTime.new(2018, 4, 26, 13, 9, 0)
      ticket = create_ticket(creation_time,[DateTime.new(2017, 4, 26, 13, 16, 0)])

      expect(ticket.latest_change).to be(creation_time)
    end

    def create_ticket(creation_time, comment_times)
      ticket = Ticket.new("Widget broken", "The widget is not loading when ...", "Philippe", creation_time)
      comment_times.each do |comment_time|
        ticket.add_comment(Comment.new("Should work now", "Dan", comment_time))
      end
      return ticket
    end
  end

end

As you can see, we have less duplication, but the tests got both unreadable and intricate … Following my advices and using more Immutable Value Objects makes the situation worse ! When data is mutable, we can customize it after the call to the Object Mother method. If data is immutable, it all has to be setup at initialization …

That’s when the mock temptation strikes. Sometimes it’s so much easier to mock a method rather than to initialize your data properly. It can be 1 line of mock instead of dealing with all this mess.

💡 If you are not careful, messy test initialization code will trick you into using mocks.

Suppose we now want to make sure we can’t add comments that were written before the ticket was created. We’ll add the following

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
describe 'Ticket Tracking' do
  # ...
  it "is not possible to insert a comment before creation data" do
    ticket = create_ticket(DateTime.new(2018, 4, 26, 13, 9, 0), [])
    expect do
      ticket.add_comment(Comment.new("Should work now", "Dan", DateTime.new(2017, 4, 26, 13, 9, 0)))
    end.to raise_error(ArgumentError)
  end
end
# ...
class Ticket
  # ...
  def add_comment(comment)
    raise ArgumentError unless @updated_at < comment.created_at

    @comments.push(comment)
  end
  # ...
end

Unfortunately, one test (latest change date is time of latest change if after comment) where we were doing just this, will now fail. The fix would be to find a real situation for this test. Here this could be that the ticket is modified after the latest comment. If the tests are too messy though, a mock can be a quick and dirty fix the setup and make the test pass :

1
2
3
4
5
6
7
8
9
it 'latest change date is time of latest change if after comment' do
  creation_time = DateTime.new(2018, 4, 26, 13, 9, 0)
  ticket = create_ticket(creation_time, [])
  comment = Comment.new("Should work now", "Dan", DateTime.new(2018, 4, 26, 13, 16, 0))
  ticket.add_comment(comment)
  allow(comment).to receive(:created_at).and_return(DateTime.new(2017, 4, 26, 13, 16, 0))

  expect(ticket.latest_change).to be(creation_time)
end

There is a third way : Test Data Builders

What are test data builders

As often, when design is not satisfying, adding an indirection solves the issue. Here the indirection takes shape of the Builder pattern.

Builder Pattern [Wikipedia] :

The intent of the Builder design pattern is to separate the construction of a complex object from its representation. By doing so the same construction process can create different representations.

The idea is to use the builder pattern to build the test data. Growing Object Oriented Software Guided by Tests covers this technique in great length.

Cover of the book Growing Object Oriented Software Guided By Tests

Here is the previous code re-written using the test data builder pattern.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
require 'rspec'
require 'date'

describe 'Ticket Tracking' do
  context "with test data builders" do

    before :each do
      @t = date_times.build
    end

    it 'latest change date is the creation date when nothing changed' do
      ticket = a_ticket.at(@t[0]).build

      expect(ticket.latest_change).to be(@t[0])
    end

    it 'latest change date is the comment date when a comment is written' do
      ticket = a_ticket
                   .at(@t[0])
                   .with_comment(a_comment.at(@t[1]))
                   .build

      expect(ticket.latest_change).to be(@t[1])
    end

    it 'latest change date is the comment date of the latest comment' do
      ticket = a_ticket
                   .at(@t[0])
                   .with_comment(a_comment.at(@t[1]))
                   .with_comment(a_comment.at(@t[2]))
                   .build

      expect(ticket.latest_change).to be(@t[2])
    end

    it 'latest change date is time of latest change if after comment' do
      ticket = a_ticket.at(@t[0])
                   .with_comment(a_comment.at(@t[1]))
                   .build

      ticket.update_description("The widget is not loading when logged in as anonymous", @t[2])

      expect(ticket.latest_change).to be(@t[2])
    end

    it "is not possible to insert a comment before creation data" do
      ticket = a_ticket.at(@t[1]).build

      expect do
        ticket.add_comment(a_comment.at(@t[0]).build)
      end.to raise_error(ArgumentError)
    end
  end
end

## Test Data Builders
##
class DateTimeBuilder
  def build
    seed = DateTime.now
    (0..10).map {|i| seed + i}
  end
end
def date_times()
  DateTimeBuilder.new
end

class CommentBuilder
  def initialize
    @at = DateTime.now
  end
  def at(time)
    @at = time
    self
  end
  def build
    Comment.new("Should work now", "Dan", @at)
  end
end
def a_comment()
  CommentBuilder.new
end

class TicketBuilder
  def initialize
    @at = DateTime.now
    @comments = []
  end
  def at(time)
    @at = time
    self
  end
  def with_comment(comment_builder)
    @comments.push(comment_builder.build)
    self
  end
  def build
    ticket = Ticket.new("Widget broken", "The widget is not loading when ...", "Philippe", @at)
    @comments.each do |comment|
      ticket.add_comment(comment)
    end
    ticket
  end
end
def a_ticket()
  TicketBuilder.new
end

## The code under test
##
class Ticket

  def initialize(title, description, reporter, creation_time)
    @updated_at = creation_time
    @comments = []
  end

  def latest_change
    ([@updated_at] + @comments.map(&:created_at)).max
  end

  def add_comment(comment)
    raise ArgumentError unless @updated_at < comment.created_at

    @comments.push(comment)
  end

  def update_description(description, update_time)
    @updated_at = update_time
  end
end

class Comment
  attr_reader :created_at

  def initialize(message, author, time)
    @created_at = time
  end
end

As you can see, it provides default test values, and we only need to provide the custom values we care about. This makes the test code both readable and intention revealing. Making the tests more understandable helps a lot to find ways to avoid mocks. Here, we replaced the mock on the comment time by an update to the ticket after the last comment.

The pattern applies in many languages, even if implementations will be different. In Ruby, libraries like factory_bot avoid a lot of boilerplate code. Have a look at this article for examples in Java.

Other advantages

Test data builders have another second effect benefit. When setting up the data is complicated, we are likely to add more that one assertion in a test. Unit tests can end up looking like a mini scenario to avoid duplicating this test setup.

It’s easy to create a specific tests for every assertion with Test Data Builders. By doing so we get smaller and more focused tests, which bring :

  • Better names for tests
  • More readable tests
  • Faster diagnostic of the problem when a particular test fails
  • 🎁 Better coverage ! In a large test, all assertions work on the same input values. When we have many small tests, we can use a different value in each.

💡 By simplifying the creation of new tests with different data, Test Data Builders increase code coverage in the long term!

Next week

This is the fourth post of a series about how to avoid mocks in automated tests. Next week I’ll dig into Custom Assertion Matchers and how they avoid mock expectations.

Immutable Value Objects vs Mocks : Fizz Buzz

In my previous post I explained how Immutable Value Objects help us to avoid mocks. In this post, I’ll illustrate this in practice with real code.

This is the third post on a series about how to avoid mocks. If you haven’t, you can start reading the full story here.

A drawing "FIZZ BUZZ" rock fallen and sealed in the ground

Fizz Buzz Example

As a simple example, I’ll go through the classic Fizz Buzz. I’ve implemented and tested it with and without immutable value objects. Please keep in mind that this is a toy example, where problems are obvious and easily fixed. I try to highlight at small scale the same problems that get hidden by the complexity of a large scale program.

Let’s start with a typical FizzBuzz implementation.

1
2
3
4
5
6
7
8
9
10
11
1.upto(100) do |i|
  if (i%3 == 0 and i%5 == 0)
    STDOUT.puts("FizzBuzz\n")
  elsif (i%3 == 0)
    STDOUT.puts("Fizz\n")
  elsif (i%5 == 0)
    STDOUT.puts("Buzz\n")
  else
    STDOUT.puts("#{i}\n")
  end
end

Suppose you need to add some tests around the code. The most straightforward way is to mock STDOUT :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
require 'rspec'

def fizzBuzz(max, out)
  1.upto(max) do |i|
    if (i%3 == 0 and i%5 == 0)
      out.puts("FizzBuzz\n")
    elsif (i%3 == 0)
      out.puts("Fizz\n")
    elsif (i%5 == 0)
      out.puts("Buzz\n")
    else
      out.puts("#{i}\n")
    end
  end
end

# main
fizzBuzz(100,STDOUT)

describe 'Mockist Fizz Buzz' do

  it 'should print numbers, fizz and buzz' do
    out = double("out")
    expect(out).to receive(:puts).with("1\n").ordered
    expect(out).to receive(:puts).with("2\n").ordered
    expect(out).to receive(:puts).with("Fizz\n").ordered
    expect(out).to receive(:puts).with("4\n").ordered
    expect(out).to receive(:puts).with("Buzz\n").ordered
    expect(out).to receive(:puts).with("Fizz\n").ordered
    expect(out).to receive(:puts).with("7\n").ordered
    expect(out).to receive(:puts).with("8\n").ordered
    expect(out).to receive(:puts).with("Fizz\n").ordered
    expect(out).to receive(:puts).with("Buzz\n").ordered
    expect(out).to receive(:puts).with("11\n").ordered
    expect(out).to receive(:puts).with("Fizz\n").ordered
    expect(out).to receive(:puts).with("13\n").ordered
    expect(out).to receive(:puts).with("14\n").ordered
    expect(out).to receive(:puts).with("FizzBuzz\n").ordered

    fizzBuzz(15, out)
  end
end

Unfortunately, there are a few problems with this code :

  • With nested logic and complicated mock setup, both code and tests aren’t very readable
  • They both seem to violate the single responsibility principle as well
  • It’s depending on a mutable output. Within a larger program, something could be messing around with this output stream. That would break FizzBuzz.

Let’s now try to use as many immutable values objects as possible, and see what happens to the mocks.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
require 'rspec'

# We extracted a function to do the fizz buzz on a single number
def fizzBuzzN(i)
  if (i%3 == 0 and i%5 == 0)
    "FizzBuzz"
  elsif (i%3 == 0)
    "Fizz"
  elsif (i%5 == 0)
    "Buzz"
  else
    i.to_s
  end
end

# We replaced the many calls to STDOUT.puts by building a single 
# large (and immutable) string
def fizzBuzz(max)
  ((1..max).map {|i| fizzBuzzN(i)}).join("\n")
end

# main, with a single call to STDOUT.puts
STDOUT.puts fizzBuzz(100)

describe 'Statist Fizz Buzz' do

  it 'should print numbers not multiples of 3 or 5' do
    expect(fizzBuzzN(1)).to eq("1")
    expect(fizzBuzzN(2)).to eq("2")
    expect(fizzBuzzN(4)).to eq("4")
  end

  it 'should print Fizz for multiples of 3' do
    expect(fizzBuzzN(3)).to eq("Fizz")
    expect(fizzBuzzN(6)).to eq("Fizz")
  end

  it 'should print Buzz for multiples of 5' do
    expect(fizzBuzzN(5)).to eq("Buzz")
    expect(fizzBuzzN(10)).to eq("Buzz")
  end

  it 'should print FizzBuzz for multiples of 3 and 5' do
    expect(fizzBuzzN(15)).to eq("FizzBuzz")
    expect(fizzBuzzN(30)).to eq("FizzBuzz")
  end


  it 'should print numbers, fizz and buzz' do
    expect(fizzBuzz(15)).to start_with("1\n2\nFizz").and(end_with("14\nFizzBuzz"))
  end
end

As we can see, using immutable value objects got us rid of the mocks. Obviously, this new code will not be as efficient as the original version, but most of the time, this does not matter. As a bonus though we get finer grain and more readable tests.

Other testing advantages

Appart from preventing mocks, Immutable Value Objects have other advantages related to testing.

  • We can directly assert their equality, without having to dig into their guts
  • We can call methods as many times as we want, without the risk of changing anything and breaking the tests
  • Immutable Value Objects are a lot less likely to contain invalid state. This removes the need for a whole range of validity tests.

💡 Immutable Value Objects simplify testing in many ways.

Convincing your teammates

We’ve seen that Immutable Value Objects have a ton of advantages when testing. People have found that they also have many other benefits :

Surprisingly though, it’s difficult to persuade programmers to use more immutability. It’s tricky to explain why returning a modified copy is simpler than just adding a setter.

💡 Why is it so hard to persuade other developers to use immutable data structures ?

I had the most success by far when encountering a bug resulting of share mutable state. When this happens, the long term benefits and safety of the immutable design wins people over. The good thing is that as you convince more people in the team, immutability will spread like a virus !

Outside of this situation, you might try some of the following arguments to move people :

  • Immutable values prevent bugs caused by different parts of the system changing the same mutable state
  • They make it easier to deal with the program in smaller parts and to reason about the system in general
  • Immutable values don’t need any synchronization and make multithreaded programming easier
  • When tempted to add a simple setter instead of keeping a class immutable, highlight the stressful debugging time to come
  • If you’re dealing with a Design By Contract adept, explain how immutability has it built-in
  • Admit that mainstream languages have bad support for Immutable Value. Point to patterns like Data Builders that work around these limitation

Next post

I’m done with immutable value objects. It was a far longer post than I thought, but there was a lot to say. This was the third post in a series about avoiding mocks. In next post, I’ll dig into another small scale mock fighting pattern : Test Data Builders.

How Immutable Value Objects Fight Mocks

Excessive use of mocks makes tests very painful to maintain. If we stick painful mocks for too long, we’ll end up abandoning unit testing. Eventually, the system will degrade into legacy. 

There are many techniques to avoid mocks. Some of the most effective involve architecture changes. Unfortunately, there are not the most straightforward to use. Re-architecting involves people and time that you may not dispose of right now. In the following posts, I’ll go over techniques that any developer can use in his day to day code to avoid mocks. These battle tested techniques that I’ve used on different projects in the past. Check the previous post if you’re interested to learn how I came to use them.

This is the second post of a series about how to avoid mocks in automated tests. If you haven’t yet, I recommend you to read my first post to understand the perils of mocks in more details.

The first mock fighting small-scale technique I’ll go over is Immutable Value Objects.

A drawing of a rock written "Immutable Value Object"

What are Immutable Value Objects ?

Behind this weird name is something very simple to understand. Immutable Value Objects :

  • Cannot change their state after construction
  • Only depend on other Immutable Value Objects
  • Don’t change the state of the whole system in any way
  • Don’t do side effects, like inputs and outputs for example

Eric Evans popularized the name in the Domain-Driven Design Blue Book. Immutable Value Objects have existed for decades in functional languages though. We say these objects are immutable (they cannot change) and pure (they cannot do side effects). Here are 2 interesting properties of Value Objects :

  • you can call a method any number of times with no risk of changing anything to the system
  • you’ll always get the same result every time you call the same method on the same object

These by itself, can already be handy when testing.

Cover of Eric Evans's DDD book

How do they prevent mocks ?

That was a bit theoretical, so let’s see how this helps to reduce mocking.

Simpler “init path”

Let’s take it the other way round and see how side effects can lead to mocking. Every test starts with setting the state in which to run the test. Side effects make this complicated, as many objects need to collaborate to set this state up. When this becomes too painful, people start hacking around with mocks. This in turn makes the tests more fragile :

  • We are not testing a “real” situation
  • We need to keep this setup in line with the real code

💡 Intricate state initialization encourage people to use mocks.

Isolates parts of the system

Unfortunately, that is not all the story ! Mutable state also, tricks us into using mocks. As soon as your test deals with mutable state, there is a chance that this state is changed in the ‘real’ system. This means that some bugs might ‘escape’ unit tests and appear in end to end tests or in production. That’s where the mocks strike ! In order to detect this bug in a fast feedback loop, we’re likely to add larger scope tests and use mocks to speed them up …

💡 Mutable state and side effects make unit tests less effective.

Reduces code with side effects

But there’s another reason why Immutable Value Objects help us to avoid mocks. As we’ll try to use them more and more for the previous two reasons, we’ll need to adapt our programming style. As we’ll push more and more code in Immutable Value Objects, the ‘imperative’ part will shrink. This ‘imperative’ part is where side-effect happen. This is the part where mocking out IOs makes sense. To summarize, the more Immutable Value Objects we use, the more isolated the IOs are, and the less mocking we need.

Javascript expert Eric Elliot also wrote about the immutability and mocks here.

Next week

This was the second post in a series about how to prevent mocks in your automated tests. Next post will be an example of using immutable value objects on the FizzBuzz kata.