Blog

Musings of a startup.

About Factlink

Factlink is a more-than-profit with a mission: to make the world more open and credible by discussing everything.

Install Factlink in your browser to discuss anything on the web. Or use Factlink on your site to get great in-line discussions.

Learn more about Factlink

« Blog

Stubbing the object under test and getting away without it

TL;DR Get better design by mocking and stubbing on the object under test while TDD-ing, as long as you don’t keep the mocks and stubs in your final (regression) test.

Stubbing methods on the object under test is highly frowned upon, but I will show you how to get better design by stubbing methods on the object under test, without delivering an unrefactorable mess.

Lately the Ruby community has focused on narrow unit tests. Started by the fast tests movement the focus has shifted from more general integration tests, to testing just one object. When writing a unit test it is very common to mock and stub the dependencies of the object. However, most people think stubbing and mocking should stop at the object boundary.

I think you should be free to continue to stub and mock all the way down to the method level, as this gives you better possibilities to design top-down (which I prefer).

Test every method, stub every method

It feels really good to test in as small as units as possible. You can clearly define what you want the unit to do, and implement it. The question is, how small do you want to make your unit? Most people stop at the object level, because you should be able to freely refactor within your objects. Because stubbing the object under test inhibits refactoring the object, most people are against it.

I disagree, to me the object boundary seems like a rather arbitrary place to stop stubbing. I think the key problem lies in the often overlooked third part of the TDD cycle. Red-Green-Refactor. You don’t stop when your test is green. Lots of time people do clean up their code, but overlook their tests. Refactoring your tests is just as important. If you follow this process there is no reason to stop at the object level. Instead we can take the methods as unit.

Therefore I propose the following:

  1. RED Test every method (in isolation, stubbing every other method)
  2. GREEN Make code work
  3. REFACTOR Refactor test until:
    • only the public interface is tested
    • no more methods on the object under test are stubbed

An example

Imagine a todo app, which supports multiple todo lists. We can send the app a todo by email “Writing: write blogpost”. This will add the todo ‘write blogpost’ on the ‘Writing’ todo list.

In this example we will use RSpec, and RSpecs stub and mock methods. You can find all code for this example on GitHub.

One convention used here is only mocking the methods we want to really test expectations on, and stub the rest. Due to this we normally only test one thing per test (though not necessarily one assert/expectation). For further reading on when to use mocks and stubs I recommend Martin Fowlers article Mocks Aren’t Stubs.

We skip the parsing of the email, and assume we get some strings list_name and todo_text from our email parser. We start by writing the test for the TodoSaver class which takes arguments list_name and todo_text, and saves the todo using the save method.


    describe TodoSaver do
      describe '#save' do
        it 'creates the todo and adds it to a list' do
          todo_list, todo = mock, mock
          saver = TodoSaver.new('Writing', 'write blogpost')
          saver.stub todo_list: todo_list

          TodoItem.should_receive(:create).with('write blogpost')
                  .and_return(todo)
          todo_list.should_receive(:add).with(todo)

          saver.save()
        end
      end
      describe '#todo_list' do
        pending "it returns a todo_list"
      end
    end
  

(source on GitHub)

Note that we expressed the save method in the assumption that we have a todo_list method. To make sure we don’t forget to create this method we added a pending test.

We can now implement the first version of TodoSaver:


    class TodoSaver
      def initialize list_name, todo_text
        @list_name = list_name
        @todo_text = todo_text
      end

      def save
        todo = TodoItem.create(@todo_text)
        todo_list.add todo
      end
    end
  

(source on GitHub)

This code will of course never work: It is missing the todo_list method. But we added a pending test to remind us of that. We will test our todo_list, to ensure it behaves like we expect.


    describe '#todo_list' do
      it 'retrieves an existing list by its normalized name' do
        todo_list = mock
        saver = TodoSaver.new('WriTing', mock)
        TodoList.stub(:retrieve).with('writing').and_return(todo_list)

        expect(saver.todo_list).to eq todo_list
      end
      pending 'creates a new list if no previous list was found'
    end
  

(source on GitHub)

To implement this, we use some TodoList class in our data layer, which can search by a normalized (lowercased) list name. We want this to prevent lists from not being found when you send in a todo with a different casing for the list. We can easily fulfill the requirements in above test:


    def todo_list
      TodoList.retrieve(@list_name.downcase)
    end
  

(source on GitHub)

We have a second case however: when the list mentioned isn't recognized, we want to create a new list. We assume that the user wants to use the casing used in the email for this new list:


    it 'creates a new list if no previous list was found' do
      todo_list = mock
      saver = TodoSaver.new('WriTing', mock)
      TodoList.stub(:retrieve).with('writing').and_return(nil)
      TodoList.stub(:create).with('WriTing').and_return(todo_list)

      expect(saver.todo_list).to eq todo_list
    end
  

(source on GitHub)

We can easily implement this alternative using a lazy or:


    def todo_list
      TodoList.retrieve(@list_name.downcase) ||
        TodoList.create(@list_name)
    end
  

(source on GitHub)

We implemented the full TodoSaver class, so this seems like a good time to refactor. I think todo_list should be a private method, but since it's tested we cannot refactor that now. Therefore we now inline the tests made for the todo_list, into the test for the save method. This means splitting one test into two tests, supporting the different scenarios tested for the todo_list:


    describe TodoSaver do
      describe '#save' do
        context 'the list to save it to exists' do
          it 'creates the todo and adds it to a list' do
            todo_list, todo = mock, mock
            saver = TodoSaver.new('WriTing', 'write blogpost')
            TodoList.stub(:retrieve).with('writing')
                    .and_return(todo_list)

            TodoItem.should_receive(:create).with('write blogpost')
                    .and_return(todo)
            todo_list.should_receive(:add).with(todo)

            saver.save()
          end
        end
        context 'the list to save it does not exist' do
          it 'creates the todo and adds it to a newly created list' do
            todo_list, todo = mock, mock
            saver = TodoSaver.new('WriTing', 'write blogpost')
            saver.stub todo_list: todo_list
            TodoList.stub(:retrieve).with('writing').and_return(nil)
            TodoList.stub(:create).with('WriTing')
.and_return(todo_list) TodoItem.should_receive(:create).with('write blogpost') .and_return(todo) todo_list.should_receive(:add).with(todo) saver.save() end end end end

(source on GitHub)

Because we don't test todo_list anymore, we can now refactor our implementation, and make all methods but save private.


    class TodoSaver
      def initialize list_name, todo_text
        @list_name = list_name
        @todo_text = todo_text
      end

      def save
        todo_list.add todo
      end

      private
      def todo
        TodoItem.create(@todo_text)
      end

      def todo_list
        existing_list or new_list
      end

      def existing_list
        TodoList.retrieve(@list_name.downcase)
      end

      def new_list
        TodoList.create(@list_name)
      end
    end
  

(source on GitHub)

The result is fully tested class, only tested by it's public methods, without stubbing methods on the object under test. This makes it easy to refactor the object.

Why do this?

Because we took the intermediate step of stubbing out methods on the object under test we were able to TDD in a top-down fashion. This meant we could postpone design decisions about data retrieval details until we actually cared about them.

I think this yields a very valid method of designing classes, which works better for me, because I like to design top-down. I am however really interested in your feedback. Do you ever use this method? Do you keep stubbed out methods on the object under test? Do you have a radically different method which yields the same results?