Mock Object Injection

Posted by James Mead Thu, 29 Nov 2007 12:17:48 GMT

A few months back, in my Introduction to Mock Objects talk at LRUG, I talked about “Mock Object Injection”. At the time I described a number of different ways of replacing a production object with a Mock Object using Mocha. I remember that at the meeting, James Adam (who has since joined the team at Reevoo) asked me why I didn’t like the Any Instance Stub Injection technique.

I’m not sure I gave him a very convincing response and I’ve been meaning for ages to have a better go at explaining what I think are the pros and cons of each of the techniques I mentioned. Here’s the list of techniques with the ones I like best at the top. I still haven’t done a very good job, but I’d be interested to hear what other people think so that I can try and improve my understanding.

Constructor Injection

The ClassUnderTest allows its dependencies to be passed in as parameters to its constructor. A mock object is passed in as a replacement for the “real” collaborator. It may be convenient to specify the production collaborator as a default parameter value.

Advantages

The dependencies of the ClassUnderTest are explicit.

Disadvantages

Can’t think of any at the moment.

  class ClassUnderTest
    def initialize(dependency = Collaborator.new)
      @dependency = dependency
    end
    def do_something
      # use @dependency
    end
  end

  collaborator = mock('collaborator')
  # constructor parameter injection
  instance_under_test = ClassUnderTest.new(collaborator)
  instance_under_test.do_something

Parameter Injection

The ClassUnderTest allows its dependencies to be passed in as parameters to the method under test. A mock object is passed in as a replacement for the “real” collaborator. It may be convenient to specify the production collaborator as a default parameter value.

Advantages

The dependencies of the method under test are explicit.

Disadvantages

Can’t think of any at the moment.

  class ClassUnderTest
    def do_something(local_dependency = Collaborator.new)
      # use local_dependency
    end
  end

  collaborator = mock('collaborator')
  instance_under_test = ClassUnderTest.new
  # method parameter injection
  instance_under_test.do_something(collaborator)

Stubbed New Method Injection

Use Mocha’s Object#stubs to temporarily replace Collaborator#new with a stub implementation that returns a mock object.

Advantages

Better than Any Instance Stub Injection, because you can have more control over different instances of Collaborator.

Disadvantages

Dependencies of the ClassUnderTest are hidden and not explicit.

  class ClassUnderTest
    def initialize
      @dependency = Collaborator.new
    end
    def do_something
      # use @dependency
    end
  end

  collaborator = mock('collaborator')
  # stubbed new method injection
  Collaborator.stubs(:new).returns(collaborator)
  instance_under_test = ClassUnderTest.new
  instance_under_test.do_something

Writer Method Injection

Use an attribute writer method to replace the “real” collaborator with a mock object.

Disadvantages

The ClassUnderTest has to unnecessarily expose a way to modify its internal state. The test is coupled to the implementation of the ClassUnderTest.

  class ClassUnderTest
    attr_writer :dependency
    def initialize
      @dependency = Collaborator.new
    end
    def do_something
      # use @dependency
    end
  end

  collaborator = mock('collaborator')
  instance_under_test = ClassUnderTest.new
  # writer method injection
  instance_under_test.dependency = collaborator
  instance_under_test.do_something

Stubbed Private Method Injection

Use partial mocking to temporarily replace a private builder method with a stubbed version of the method.

Disadvantages

The test is coupled to the implementation of the ClassUnderTest. The partial mocking of the instance_under_test means that the test is not testing a pristine instance of the ClassUnderTest, but a modified one. It also means that the boundaries between test code and production code are less clear.

  class ClassUnderTest
    def do_something
      local_dependency = build_collaborator()
      # use local_dependency
    end
    private
    def build_collaborator
      Collaborator.new
    end
  end

  collaborator = mock('collaborator')
  instance_under_test = ClassUnderTest.new
  # stubbed private method injection
  instance_under_test.stubs(:build_collaborator).returns(collaborator)
  instance_under_test.do_something

Any Instance Stub Injection

Use Mocha’s Class#any_instance method to temporarily replace the method on a collaborator with a stub method.

Disadvantages

The stubbed method is applied to all instances of the collaborating class. If the instance_under_test interacts with the stubbed method on more than one instance of the collaborating class, it isn’t possible to specify different behaviour for the stubbed method on each instance. Even if the instance_under_test only interacts with the stubbed method on one instance of the collaborating class, the test is specifying more stubbed behaviour than strictly necessary which could lead to false positives.

  class ClassUnderTest
    def do_something
      local_dependency = Collaborator.new
      return local_dependency.do_stuff
    end
  end

  # any_instance stub injection
  Collaborator.any_instance.stubs(:do_stuff).return('something useful')
  instance_under_test = ClassUnderTest.new
  instance_under_test.do_something

Instance Variable Set Injection

Use Object#instance_variable_set to replace the reference to a collaborator with a mock object.

Disadvantages

The test is coupled to the implementation of the ClassUnderTest. In particular the test is coupled to the supposedly private instance variable. In my opinion, it would be more honest to expose the instance variable by adding an attribute writer and using Writer Method Injection.

  class ClassUnderTest
    def initialize
      @dependency = Collaborator.new
    end
    def do_something
      # use @dependency
    end
  end

  collaborator = mock('collaborator')
  instance_under_test = ClassUnderTest.new
  # instance_variable_set injection
  instance_under_test.instance_variable_set(:@dependency, collaborator)
  instance_under_test.do_something

Tags , , , , , ,  | 1 comment

Mocha in Java using bytecode manipulation

Posted by James Mead Sat, 16 Sep 2006 14:43:00 GMT

My former colleague and bytecode maestro Stacy Curl has an interesting article explaining how it would be possible to implement Mocha in Java using experience from his PicoUnit project.

It’s good to be reminded that meta-programming is possible in Java, but just a bit more effort.

Tags , , , , , , , , , ,  | no comments

Fed up with Rails fixtures? (part two)

Posted by James Mead Mon, 14 Aug 2006 03:24:00 GMT

Previously I described how you can write unit tests for ActiveRecord models without using fixtures – just using models in memory. Although only a limited subset of tests can be written this way, it’s often overlooked.

Another technique is to use constructor-based dependency injection (DI) in combination with mock objects. However, thanks to ActiveRecord::Associations::AssociationProxy#raise_on_type_mismatch it isn’t straightforward to use mocks for associated models. However, DI can be used effectively for ActionController controllers:

class ArticleController < ApplicationController
  def initialize(article_class = Article)
    super()
    @article_class = article_class
  end
  def show
    @article = @article_class.find(params[:id])
  end
end

The controller constructor defaults to using the real Article model class, but in the test we’re going to inject a mock object in its place. You could use one of the many mocking libraries for this, but I’m going to use our home-grown one, Mocha, in a “functional” controller test.

require File.dirname(__FILE__) + '/../test_helper'
require 'article_controller'

class ArticleController; def rescue_action(e) raise e end; end

class ArticleControllerTest < Test::Unit::TestCase
  def setup
    @article_class = mock()
    @controller = ArticleController.new(@article_class)
    @request    = ActionController::TestRequest.new
    @response   = ActionController::TestResponse.new
  end 
  def test_should_display_article
    article = Article.new
    @article_class.expects(:find).with('1').returns(article)
    get :show, :id => 1
    assert_template 'show'
    assert_equal article, assigns(:article)
  end
end

I wrote this at about 6am on the train from Durham to London without the aid of a coffee, so it may not make much sense!

Tags , , , , , , , ,  | 5 comments