Hello and happy Friday!
Today we’ll be covering a short refresher on how to put together an Rspec test for the data-model. If you missed yesterday’s post on setting up a test environment, you can take a look here.
Components of a Spec
There are 3 components that make up any good spec, they are the setup, the execution and a verification of desired results.
- The setup: Is where we establish the scenario that our individual spec test will cover
- The execution: Entails us creating a method call that triggers the spec call in our model to run
- The verification: Is where we make sure that what we expect to happen actually occurs from our test results
Here is a past post I wrote, covering my first attempts at learning to write good model tests.
Anatomy of a Data-Model Spec
Think of testing our data-models as a time to evaluate the building-blocks of our project application. I’ve been taught that we should be testing for behavior and not implementation. I stumbled upon this detailed StackOverflow post on the subject.
Now that we have a little theory to go on, lets take a look at how our specs should look like syntax-wise.
This is the basic structure of a spec_file. We require our spec_helper file and then define a describe block of the class we’re planning on adding specs inside of.
I found that it’s quicker for me to layout what I want to test for first, and only once the ‘what’ is defined, go into the implementation levels. Here’s an example from my current project lunch_panoply that demonstrates what I mean by separating out the ‘what’ (i.e. the blueprint) and the ‘how’ (i.e. the implementation details).
Above is a snippet (it contains most but not all of the specs for this model) of how I’m learning to approach building out a model spec in Rails.
Not taking the first spec into account on lines: 4-7, we can see that I’ve created a long list of pending specs. These pending specs are going to be the blue print I follow to cover the behaviors expected in the Venue class. Laying each of the individual specs in this way allows me to focus on one level of detail at a time, before any implementation code is addressed, I can now look over these pending tests and first ensure that I have the desired coverage handled.
If I ran this spec file with zeus, we can verify that there should be 1 passing and several pending specs:
Fantastic! 27 out of 28 specs are pending and the 0 failures you see in the above example means that our first spec is passing. I will run tests early and often, as doing so serves as a constant reminder of where I’m at in the process. Its also kind of neat to treat these similar to a check-box or to-do list. Watching one after the next spec pass, as we test-drive closer to app behavior coverage one model at a time.
Now that we understand how to setup our model specs with a containing
describe block, and have seen how I personally enjoy laying out pending specs first in order to ensure from my perspective that I’m adding the proper coverage, we can get started on actually doing a couple of examples… Red, Green, Re-factor style of course!
I’ve started out by creating a spec to ensure that my factory is in-fact valid. If you find that your specs used to run and pass with flying colors only to all fail after a brief stint away from your computer, there are two quick troubleshooting areas I’ve learned to be aware of.
The first is to ensure that our test database is up to speed with our development database. What I mean by this is that as we migrate additions and subtractions to our data-model, the command
rake db:migrate will make changes to our development database, but in order for those changes to be in tandem in our test-suite, we also will need to type the command
FactoryGirl and Faker in Action
If our test database is up to speed and your tests are all still unexpectedly failing, the second place I look is the factory of the spec_file(s) in question. Here’s the factory I’ve created for Venue:
First thing I wanted to point out is that we’re using
require 'faker' at the top of the file so that we can generate some ‘sample-data’ for our class attributes. This not only makes our process a little less burdensome, it can be downright humorous at times to read some of this ‘sample-data’ automatically generated for us by our friend the Faker Gem.
The block that starts with
FactoryGirl.define on line: 3 in the above example, is just a container to house the actual factory we are defining. Much in the same manner as we created a data-model block to house our individual specs earlier in this post.
Next up is to define the factory that will hold attribute data for our tests.
factory :venue is how we actually define our factory, note that the
v passed into our block isn’t necessary, and can be completely removed. It’s left in the examples because I’m researching a purpose for it, maybe it helps during association with other classes? I’m still working on understanding that.
Here is the example again in order to move ahead with our factory building walkthru:
Note that there are 3 lines where I removed the Faker Gem’s call to a method and instead statically coded in string values. This was due to a the
:zip attributes needing to have explicitly defined lengths for test purposes. Testing behavior in this manner means that I just don’t want to expect the presence of data inside of these attributes, but also ensure that there is some standard validity to the behavior. In these cases, the size or length of the user data submitted for these attribute fields.
You can get a list of Faker method calls here. There are quite a bit of options available to satisfy our faker data-generation sweet tooth.
Time to Test-Drive
Let’s get started with implementing those pending specs in our
Just as a reminder, I like to think of it as sort of a score board, I’ve added the above illustration that demonstrates how many specs we need to implement.
Off to the races!
context block you’ll see below, is there to keep our tests easy to read by making them more organized. In the following example, I’ve grouped specs with valid credentials together, as well as a set of specs that assert what invalid credentials would look like.
We can achieve DRYer specs using
- Describe: Can be used to outline the general functionality of a class, is used for things
- Context: Outlines a specific state by grouping related tests together (i.e. valid/invalid in my case)
- Subject: Used for the thing we’re testing, specifies the thing to test
- Before: Runs code before each spec, is used for actions
- Let: Lazily runs code when it’s first used, and defines a named variable
- After: Used when a spec requires post-example teardown
- Since Rspec handles cleaning up the database by default,
afterisn’t used as frequently
Let’s get started on the first spec in the
context of having valid credentials.
First I’m adding the familiar
end blocks so that I can add some implementation details inside. Next I’m creating a factory instance and assigning it to a local variable named
venue. Note that I’m using some truncated syntax to create an instance of
:venu through FactoryGirl. Thats one of many benefits I enjoy about the FactoryGirl gem, it was truly written with the developer in mind. If you’re not sure how to go from the full syntax of
venue = FactoryGirl.create :venue to
venue = create :venue here’s how I was able to do that.
Here’s an illustration of the spec_file I showed in yesterday’s post. The code we want to hone in on can be seen on line: 13 in the image above. Adding this will allow you to have an additional option of the truncated syntax
venue = create :venue as well as allows you to fall back to the full syntax if you so desired
venue = FactoryGirl.create :venue.
:create method isn’t alone in FactoryGirl land, its a way to create but not persist an object in our test database. During those times we need to have a record or set of records persist, we can use the
:build method in the exact fashion as
Back to Spec Building
So we’ve created a test-object with a factory instance that we’ve defined, next is to make a call to the desired attribute.
We could have also overrode our
:name attribute’s value by passing in a second argument in the
FactoryGirl.create method like so:
I’ve only commented out line: 12 because I find leaving the attribute in question on its own line increases my readability at a glance. It’s just a matter of personal preference though, you can do whichever way works best for you.
The final step is to assert our desired behavior works. Here’s what that line of code looks like:
Now to run our specs to see what happens.
And Fail! Just what we expected, to be sure that this isn’t a typo somewhere, let’s look at our Venue model and see if we have any validations defined?
This is how test-driving an application is supposed to work. First we write the test, receive the fail, and then write just enough code in our model to make the spec pass. So let’s get too it.
Looking at line: 7, we have now added a validation for the presence of data for the
Let’s see if our spec is still failing:
We’ve gone from 27 pending specs to 26, and have no failures. Lets take an opportunity to peak at what a spec looks like with invalid credentials.
Here’s what the spec in pending status looks like,
and here is the same spec with the implementation steps completed.
Now lets run our tests to see where we’re at:
And Yahtzee! We’ve gone from 26 to 25 pending specs with 0 failures.
As a fun little TGIF experiment, lets see how long it takes me to complete the venue_spec.
Starting the timer… Brought to us by Apimac Intuitive Apps.
And now I’m done!
Here’s the Rspec terminal output from my venue_spec file:
A look at my completed spec file:
And a snapshot of the time this took:
I hope that this post has given you a well-illustrated walk-thru of what it looks like to test-drive the data-model layer of a Rails application. If you have anything to add to this walkthrough, feel free to leave a comment.
Here is a AirPair from Edward Anderson, one of the many mentors that have been invaluable during this ongoing process of my efforts into becoming a confident Ruby and Rails developer. We’ll be covering a refactor of the model specs in a future post.
Have an enjoyable weekend!