Ruby on Rails and automated testing are going together, hand in hand. As many language programming, Rails has it's own testing framework. It support automatically to create boilerplate test files. Yes, many people and companies don't implement automated testing in their own project. It's because of many reasons. One of those reasons is they are startups and they do want to focus on speed in development. But automated testing is the way in order to make our projects reliable, testable. It's very important in software development. We need evidence to prove that our software is reliable and every time we make changes, we do want to make sure that those changes don't affect our existing system.
When we look at Rspec, we see that there are a lot of syntaxes to learn. Don’t be afraid even Rspec is a complex framework. It’s like the 80/20 rule, we just use 20 percent of one hundred percent.
So, what is the right way of testing? In my opinion there are three approaches:
- Tests should be reliable.
- Tests should be easy to write.
- Tests should be easy to understand today, and in the future.
Tests really give us the confidence. Every time we make changes, we run the tests and all of cases are passed, we believe our changes are reliable. They don’t make bad effect on our system.
Setting up environment
Firstly, we need to set test database up in database.yml. We install RSpec with the following command line directive:
$ bin/ rails generate rspec:install
In order to get a configuration file for Spec (.rspec) and a directory spec and two helper files. Let’s edit .rspec file so see the test cases with highlight:
Next we make test suite faster at start times with the spec bin stub. Add the following dependency to your Gemfile:
Inside the development-group:
bin/rails g spec:model user
Run bundle to install the new dependency and generate the new binstub by:
bundle exec spring bin stub spec
The idea here is we take advantage of faster application boot times via Spring. When we start the development server or running a Rake task, the test suite will kick off more quickly since the application is already running.
Next add these configuration to config/application.rb
Let's take a look at a few parameters:
- Fixtures: false
skip adding files to simplify creating objects in the test database. Must be true if want do want to use factories to facilitate such data.
- view_specs: false
skip generating view specs.
- help_specs: false
skip generating view specs.
- routing_specs: false
omit a spec file for config/routes.rb.
Model and Controller specs will be created by default. If you want to skip the controller specs, you’d add controller_specs: false. As we can see that we skip the views specs. Why do we do that? Because creating view tests is a hassle. We always try to do something new, make changes on views. Thus, maintaining them is worse.
A model spec should include tests for the following:
- When instantiated with valid attributes, a model should be valid.
- Data that fail validation should not be valid.
- Class and instance methods perform as expected.
Now take a look on the model spec:
describe User do it ‘is valid with a first name, last name, email, and password’ it ‘is invalid without a first name’ it ‘is invalid without a last name’ it ‘is invalid without an email address’ it ‘is invalid with a duplicate email address’ it ‘returns a user’s full name as a string’ end
We have four best practices on this Model:
- Describing a set of expectations like an outline - how it look like? How it behave?
- Single responsibility - each “it” example only expects one thing.
- Each example is explicit - the text after “it” is optional but we required them to make our example is explicit, easy to read and understand at the first glance.
- Example begins with verb - it’s is readability.
We can generate the model by using this command:
bin/rails g spec:model user
Cases for testing:
- Testing validations: object.valid?
- Testing scope:
expect([Model].search(“first”)).to include(object1, object2)
search scope returns a collection of Model matching the search term.
- Testing for failures:
DRY specs with describe, context, before and after
Describe and Context are technically interchangeable. Describe is usually used for general functionality of class or feature or even instance name. Context outlines a specific state, usually starts with “when”. Code inside before block is run before code inside individual tests.
before do # set ups test data for all tests in the file end describe “search message” do before do end context “when…” do end context “when…” do end end
Before is a good way to recognize and clean up redundancy in our spec:
- before(:each) or only before
runs before every example.
- before(:all) or before(:context)
runs once before all tests in describe or context block.
runs before entire suite of tests, across all files.
We can speed up test by taking advantage of before(:all) and before(:suite) in order to isolate expensive test setup to a single run. However, be careful with that, because it will make the changes before all tests. Some test might be affected but we didn’t anticipate.
DRY in test
- Consider duplicating your test data setup if you need to scroll up and down in a large spec file in order to see what it is you’re testing.
- Try to express your variable and method names for easy understanding.
- Use active, explicit expectations.
- Test for both two aspects: what you expect to happen and what you expect to not happen.
- Test for edge cases: min and max for example.
- Organize your specs for good readability.
Creating meaningful test data (coming soon)