Start Mocking

I recently began converting tests in a project from using fixtures to relying more on mocking and stubbing. I'm using RSpec for one project, FlexMock with normal Test::Unit for another. Although using mocks and stubs provides numerous advantages, here is a simple example which should convince you to start mocking:

Let's say you want to test a 'create' controller action which should have one behavior if the record passes validation and saves, and a different behavior if validation fails. Without using mocks, you need to have knowledge of your model validations to test the behavior. What if the model currently has no validations but will in the future? This would make it more difficult to test the controller's behavior when model validation fails. However, if you're using mocks...

Example with FlexMock:

Controller:
  def create
    @model = Model.new(params[:model])
    if @model.save
      redirect_to :action => 'show', :id => @model
    else
      render :action => 'new'
    end
  end

def test_create_if_validation_fails
  mock_model = flexmock('model')
  mock_model.should_receive(:save).and_return(false)
  flexstub(Model).should_receive(:new).and_return(mock_model)

  post 'create'
  assert_response :success
end

def test_create_if_validation_passes
  mock_model = flexmock('model')
  mock_model.should_receive(:save).and_return(true)
  flexstub(Model).should_receive(:new).and_return(mock_model)

  post 'create'
  assert_response :redirect
end

This is a simple example of testing your controller without reliance on your models. Mocks allow you to focus on the interaction between the controller and the model, and removes the dependency on model implementation (you should be testing your models in unit tests, not in functional tests!). As a side bonus, your tests will run faster since you're using mocks instead of fixtures and database queries.

A caveat: if you try this exact example with normal Rails testing, the test for if validation fails will most likely fail. Your view will try to use @model, calling a method on your mock which is not expected, which will raise and result in a 500 error instead of a 200 OK / Success. I will post more on that later.