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.

Stay tuned ! !

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.

Careless Mocking Considered Harmful

💡 Mock hell : when excessive use of test mocks makes refactoring extremely slow or difficult.

A few years ago, I managed to get a side project out of mock hell. Since then, I’ve been using what I learned to avoid mocks in all the projects I’ve worked on. This is the start of a series of posts about my mock-avoiding techniques.

A tag "Mocks don't rock !"

Escape from Mock Hell

Between 2010 and 2014, I was working on a side project I called http://mes-courses.fr. Which actually means “my house shopping” in English. I wanted people to be able to do their house shopping in 5 minutes, by using a better UI for online groceries. I was using Ruby, and I had just read Growing Object Oriented Software Guided by Tests. I got a bit too excited with mocking, and was using it way too much.

I’d been practicing Test Driven Development for more than 5 years and I was expecting great results with a language like Ruby. After a few months though, I could feel that something was not going as well as it should. The test initialization code was getting longer and longer, as it included a lot of mock setup. This made the tests more complex and less readable. It also made them unreliable, as it was not rare for all my unit tests to pass while the system was not working. I was taking the habit of running my end to end test more and more often. I was also losing a lot of time maintaining the mock setup code in line with the real classes. Mocks also tricked me into the bad practice of keeping a 1 to 1 mapping between code and test files. That again increased my maintenance burden when moving code from one file to another.

It reached a point where I could not take it anymore. All these problems were pointing at mocks, so I tried to remove them from a test file. Here are the techniques I ended up using to remove them mocks : 

The end result was beyond my hopes, as my problems almost magically disappeared. The code got simpler, I became a lot more confident about my unit tests, and they got easier to maintain. As an illustration, here is an excerpts from the diff of a rails controller test file which went through this mock diet.

A screen capture of a Github diff showing a test file going on a mock diet

What’s the long term risk ?

Basically, excessive mocking arms the maintainability of the tests. Here is what would have happened if I’d done nothing. Tests would have become so painful to maintain that I would have started to ignore or delete them. As coverage would decrease, more and more code would become untested. That’s exactly Michael Feathers’ definition of Legacy Code :

Legacy Code is code without tests. Michael Feathers

To summarize, excessive use of mocks leads to legacy code ! As most of us have learned the hard way, the further a system drifts into legacy, the lower the productivity.

💡 Excessive use of mocks leads to legacy code

Next posts

Others already spoke about the dangers of mocks :

In this series of posts, I’ll go through the details of the different techniques I used to remove mocks. Here is my plan :

  1. Careless Mocking considered Harmful
  2. How Immutable Value Objects fight mocks
  3. Immutable Value Objects vs Mocks : Fizz Buzz
  4. How to use Test Data Builders to avoid mocks and keep your tests clear
  5. How Custom Assertion Matchers will keep mocks away
  6. Avoid mocks and test your core domain faster with Hexagonal Architecture
  7. Get rid of mock maintenance with full fledged in-memory fakes
  8. When is testing using mocks still a good idea ?

Frequently Asked Questions About the 20 Hours of Code Katas

In my previous posts, I explained how to use the 20 hours of Code Katas technique to learn new languages. If you did not read these yet, start by the beginning.

A drawing of FAQ in a lightbulb

To close this series, here are a few tips and suggestions presented as questions and answers.

What if you don’t know TDD yet ?

The few Parisian guys who invented the Coding Dojo wanted to teach and spread TDD ! You should have no problem to use it to learn TDD yourself !

💡 The coding dojo was invented to teach and spread TDD

Pick your favorite language, and schedule a kata plan to practice TDD. Watch one or two videos to see how gurus are doing it. At first, you’ll have to be very careful to stick to baby steps and the red-green-refactor loop. If you need help, check meetup.com for local coding dojos where you’ll find help.

Can I apply this technique to learn something else than a new language ?

As you might have noticed, I used it to refresh my Javascript. I went on to learn different flavors of JS, but also different test libraries. I’ve used in to learn more advanced parts of other languages in the past.

Katas also work well to learn programming techniques like refactoring or DDD. Some nice people shared refactoring katas on the web. To practice DDD, we could repeat katas with the constraint of using Entities and Value Objects only.

You can even use the technique to learn other things like frameworks or tools, but you’ll need to tune it. As I explained before, you need an exercice for deliberate practice and a fast feedback loop. We typically use a Code Katas and TDD for that, but that’s not the only options. Whenever you can find a way to deliberately practice with a fast feedback loop, you’re ready to go ! These days, we should look for docker images with frameworks and tools pre-installed. Going through tutorials without looking at the solutions is deliberate practice. A small live environment can give us fast enough feedback.

💡 Find Deliberate Practice exercices and a fast feedback loop for efficient learning

What if I don’t find any kata ?

Build one yourself ! I’m not joking, building a kata, especially one where you start from scratch is not too difficult. Inspiration comes from anything you happen to do in your daily work. Trim down a programming challenge you had to work, and you might have a kata ! Went to a programming interview ? The question you had to answer might do a nice kata.

"Make things happen" written on a blackboard

Once you’ve created and tested your kata, share it ! There are online kata repositories where you could get a chance to publish it.

One last thing

I just remembered I did not finish my story about my Javascript kata plan. For those wondering, here is the end of story. In the end I did not join this team to do Javascript coaching. After thinking through it for a while, I decided to stop the katas there, and move to something else. I was only 6 hours in, and what was the point to study Javascript not to use it straight away ? The day I’ll need it, I’m likely to have forgotten 80% of it and some of it will be outdated. The knowledge is only another 20 hours away anyway !

That’s what we could call “Just In Time Learning” ! We are drowning in knowledge nowadays. It’s better to have a fast and effective way to learn anything than trying to know everything.