No Tests? No Excuses!

There is a strong testing vibe in our community. It’s a frequent topic in local groups, conferences, and interviews. Even with that drive in our community it’s common to land projects that have no working tests.

If you are only used to unit testing then it may seem overbearing to trash the existing test suite and start fresh. That’s because it is! It is impractical to delay your project to comb through every class and define it’s behavior in a test.

Getting a test suite quickly is possible when approaching it from the angle of acceptance testing. Ensuring the details are working together instead of testing them independently.

For example, when you hit a piñata, do you get candy? We’re not ensuring the piñata is mounted, that it can take a couple of whacks, or that the candy is delicious. We have a working example and can assume those attributes are true. If one of them is off the entire process fails.

In a Ruby on Rails project I can spend an afternoon and get a decent coverage of an applications expected behavior. Here is a sample of testing a home page using Rspec.

I’m not testing for every working component . This asks if the someone requests the homepage, do they get the elements that make the homepage. It is much easier to do a quick analysis and implementation with this method. I don’t even have to look at the source code to define expected behavior and ensure it doesn’t change.

This has an advantage in a refactoring scenario. It doesn’t matter how much we change the underlying code, the results of the homepage are expected to the same. Having integration tests gives us the confidence to strip out any code and give us quick feedback on the consequences.

 

Test Doubles

The other day I broke our tests by including a Module that required configuration. I didn’t realize this until our Continuous Integration setup reported failures. If I wanted to get my pull request through I would need those tests to pass. One dev recommended a test adapter. I began designing and thinking of a solution to get this module working while testing.

However, another dev had a much simpler solution and resolved it with a single line in the spec:

allow(Mail::NewUser).to receive(:send)

RSpec refers to this as a Test Double, after Martin Fowlers article. The idea is that our tests are specific to a single value or behavior. However, these can unexpectedly change when they depend on another object’s value or behavior. If we can replace that dependency with an object providing consistent behavior we can keep our tests simple and clean.

Suppose I want to test a Hello module. It accepts an instance of Person and says hello.

module Hello
  def Hello.person(person)
    "Hello #{person.name}"
  end
end

An issue with testing this module is that the Person class is complex. While person.name is expected to be a short string, it’s a complicated algorithm to generate and create.

This shouldn’t be an interruption to creating the Hello test. It’s also not the time to think about the Person class. How can we test the Hello module’s behavior without having an instance of Person readily available?

Below I have created two tests. The first uses an Open Struct to provide the needed behavior to test Hello.person. The second uses RSpec’s doubles to create an instance of Person. It also has a ‘name’ method to call and return the value ‘Dave’.

RSpec.describe "Hello" do
  it "accepts a dummy object to greet" do
    require 'ostruct'
    dave = OpenStruct.new(name: 'Dave')
    expect(Hello.person(dave)).to eq("Hello Dave")
  end

  it "uses a test double to greet" do
    dave = double("Person", name: "Dave")
    expect(Hello.person(dave)).to eq("Hello Dave")
  end
end

These tests are only concerned with the Hello Module. They do it with minimal knowledge of what the Person class is. Whatever happens to Person, this test double will always remain the same, leaving our tests safe and happy.

Test doubles are beneficial.

  • They enable you to be lazy. In our example we didn’t need to know the Person object to test Hello.
  • They prevent things from creeping into our tests that we are not testing.

I hope this overview of Test Doubles helped you understand testing. I recommend the RSpec Mock documents for a full overview of RSpec and test doubles.

http://www.relishapp.com/rspec/rspec-mocks/v/3-5/docs

Uncle Bob has also written a helpful article on the strengths and weaknesses of mocks, https://8thlight.com/blog/uncle-bob/2014/05/10/WhenToMock.html.