At the Forge - RSpec

 in
A new way of looking at testing.
Model Testing with RSpec

Let's create a slightly more sophisticated version of the person model:

./script/generate rspec_model person first_name:text \
    last_name:text email_address:text phone_number:text \
    sex:text

This creates a migration, which you can use to create the first version of your person model:

rake db:migrate

Now, it's true that you should go into the migration file and modify things, such that (for example) the person's name, e-mail address and sex are all mandatory. However, let's ignore that step for now and assume that you want all of your validation logic to be at the application layer. In such a case, you would want to put some validations in the model file.

Well, you could do that, but that wouldn't be very BDD of you, would it? Rather, you should imagine the specification that a consumer, or the manager, might want from a “person” object, and then build the object up to adhere to those standards.

For example, you might want to ensure the presence of the first and last names. So, the first file to modify is spec/models/person_spec.rb, rather than app/models/person.rb. (For reasons I don't quite understand, Test::Unit calls model tests unit tests, and RSpec calls them model tests, and the controller tests are called functional tests.) If you open that file, you'll see a new, bare-bones specification:

require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')

describe Person do
  before(:each) do
    @valid_attributes = {
      :first_name => "value for first_name",
      :last_name => "value for last_name",
      :email_address => "value for email_address",
      :phone_number => "value for phone_number",
      :sex => "value for sex"
    }
  end

  it "should create a new instance given valid attributes" do
    Person.create!(@valid_attributes)
  end
end

You can run your full suite of specs at any time, by typing:

rake spec

The first line imports anything defined in spec_helper, which I mentioned earlier. Next comes a describe line; this will be familiar to those of you who have looked at Shoulda. The basic idea is that someone reading the specification reads the argument to “describe” and then reads each of the individual specifications that start with “it”. In other words, this spec file tries to say “Person should create a new instance given valid attributes.” And, sure enough, it does.

The before(:each) block tells RSpec what it should invoke before each “it” block. This ensures that the @valid_attributes instance variable will be set to a predictable value before running each spec. You then can modify @valid_attributes as necessary within each spec, as you will soon see.

The thing is, you're checking the validity of your specification by creating a new instance of Person. You can do that, but if the spec fails, you will end up with a code backtrace mixed in with your report. For this reason, I'm going to change the existing spec definition to look like this:

it "should create a new instance given valid attributes" do
  p = Person.new(@valid_attributes)
  p.should be_valid
  p.save.should_not == false
end

Instead of Person.create, you now are invoking Person.new, assigning it to the variable p. Let's check p in two different ways, once using should and the other using should_not. These methods are mixed in by RSpec to the Object class and contain a great deal of behind-the-scenes magic to make specifications readable, almost as if they were in plain English. For example, when you say:

p.should be_valid

RSpec's should method looks for a method named valid? for that object and checks that the invocation of this method returns true. This works for any predicate (that is, method that returns true or false). If should or should_not is followed by be_XXX, RSpec turns that into a method call of XXX? on the object instance.

So, you can understand what it means to say:

p.save.should_not == false

which you equivalently could write in a more positive, optimistic way:

p.save.should == true

In both cases, you invoke the save method on the object and check that its returned value is true. You might argue that you don't need to invoke both new and save on your object, but I like to make sure the object is valid in both Ruby and the database. After all, it could be that you told the database to reject null values, but that you allowed it using validations in your ActiveRecord definition.

Now let's move a bit beyond the defaults to set some limits on attributes. Presumably, you want people in your database to have all of these fields (first name, last name, e-mail address, phone number and sex) defined. If you were developing in a non-TDD/BDD way, you first would set up validations for all of those and then add some tests. But, here you're trying to write tests first, thinking from the “outside” how your object might behave. And indeed, each person should have a first name, a last name, an e-mail address and a telephone number. (Strange as it might seem now, there was once a time when having an e-mail address was not expected.)

So you could, for example, include the following:

it "should not be valid without a first name" do
  @valid_attributes.delete[:first_name]
  p = Person.new(@valid_attributes)
  p.should_not be_valid
  p.save.should == false
end

In other words, you take @valid_attributes, remove the :first_name key from it and then create a new person with the rest of the name-value pairs from @valid_attributes. This should not work, because everyone needs a first name. But when I run the specs, I get:

1)
'Person should not be valid without a first name' FAILED
expected valid? to return false, got true
./spec/models/person_spec.rb:23:

Finished in 0.038731 seconds

2 examples, 1 failure

In other words, the specification failed. But that's okay—that's precisely what you want when you're working in BDD fashion. You wrote a test, it failed, and now you can go into the code and modify it, so as to ensure that the test passes. Ensuring that this current test passes is a simple matter of adding a validation to your ActiveRecord model. Instead of being the empty default:


class Person < ActiveRecord::Base
end

______________________

White Paper
Linux Management with Red Hat Satellite: Measuring Business Impact and ROI

Linux has become a key foundation for supporting today's rapidly growing IT environments. Linux is being used to deploy business applications and databases, trading on its reputation as a low-cost operating environment. For many IT organizations, Linux is a mainstay for deploying Web servers and has evolved from handling basic file, print, and utility workloads to running mission-critical applications and databases, physically, virtually, and in the cloud. As Linux grows in importance in terms of value to the business, managing Linux environments to high standards of service quality — availability, security, and performance — becomes an essential requirement for business success.

Learn More

Sponsored by Red Hat

White Paper
Private PaaS for the Agile Enterprise

If you already use virtualized infrastructure, you are well on your way to leveraging the power of the cloud. Virtualization offers the promise of limitless resources, but how do you manage that scalability when your DevOps team doesn’t scale? In today’s hypercompetitive markets, fast results can make a difference between leading the pack vs. obsolescence. Organizations need more benefits from cloud computing than just raw resources. They need agility, flexibility, convenience, ROI, and control.

Stackato private Platform-as-a-Service technology from ActiveState extends your private cloud infrastructure by creating a private PaaS to provide on-demand availability, flexibility, control, and ultimately, faster time-to-market for your enterprise.

Learn More

Sponsored by ActiveState