Unit test quickie

October 23rd, 2007

Having your unit tests isolated is something to strive for. Why? because ideally, when you (or someone else) introduces a bug to the system, only a single unit test would fail, thus leading you to the source of the error quickly. If an error triggers hundreds of tests to fail, you’d have to spend a good time looking for the source of the error.

Mocks to the rescue, again. Do you have the following pattern recurring in your unit 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
describe Project, "(valid)" do

  it "is valid if it has a name and an owner" do
    Project.new(:name => 'Death Star II',
                :owner => 'moff.jerjerrod@ct.empire.mil').should be_valid
  end

  it "is NOT valid if it is missing name" do
    Project.new(:owner => 'moff.jerjerrod@ct.empire.mil').should_not be_valid
  end

  it "is NOT valid if it is missing owner" do
    Project.new(:name => 'Death Star II').should_not be_valid
  end

  it "can contain issues" do
    issue = build_an_issue(:summary => 'proton torpedo in the ventilation shaft might cause havoc', :severity => 2)
    proj = build_valid_project
    proj.issues << issue
  end
  
  def build_valid_project
     Project.new(:name => 'test project', :owner => 'project@man.ag.er')
  end
...
end

So do I. But there’s one tiny problem if you have lots of tests and the validation code gets more complex. If the validation fails, all the tests using build_valid_project could fail as the project returned by the method wouldn’t be valid anymore. So, time to flex your mocking skills again:

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
describe Project, "(valid)" do

  it "is valid if it has a name and an owner" do
    Project.new(:name => 'Death Star II',
                :owner => 'moff.jerjerrod@ct.empire.mil').should be_valid
  end

  it "is NOT valid if it is missing name" do
    Project.new(:owner => 'moff.jerjerrod@ct.empire.mil').should_not be_valid
  end

  it "is NOT valid if it is missing owner" do
    Project.new(:name => 'Death Star II').should_not be_valid
  end

  it "can contain issues" do
    issue = build_an_issue(:summary => 'proton torpedo in the ventilation shaft might cause havoc', :severity => 2)
    proj = build_valid_project
    proj.issues << issue
  end

  def build_valid_project
     returning Project.new do |proj|
        flexmock(proj, :valid? => true)
     end
  end

Now the project returned by build_valid_project would seem like a valid project; even Rails would consider it valid all right. Which is a good thing, because we created the build_valid_project method in the first place to, ummm, quickly set up a valid project we can use in several specifications. But because we stubbed ActiveRecord::Base#valid?@ we would really have to test the idea of a valid project somewhere. That’s what we do in the first three tests (and would add more if we needed, but only once).

Conclusion: mocking stuff helps you keep your test assertions krhm, spec expectations more DRY.

Leave a Reply