This article requires basic experience of TDD using UnitTest in Rails.
I found many developers being confused by all the levels of testing in Rails, and some concepts in software testing. Rails community has a very very strong awareness of TDD, and even BDD. Apps are designd using an Outside-in approach, which makes me feel really comfortable and also effective.
Test suite is built while developing, not after developing. Although a test suite is not a silver bullet to 100% ensure the quality of your app, it does increase your confidence.
There is a voice saying writing test suite sometimes makes development less effective. In practical yes, I have the same feeling sometimes. But it is not the test suite to be blamed, but the way to write it. Don't write the test cases for something you are already very confident with. More test cases you write will not prove you are a good TDD practitioner.
I meanly use RSpec + FactoryGirl. There are bunchs of good testing frameworks out there, such as Cucumber, for Acceptance test. I only develop small or medium sized app, and I feel Cucumber will make my development less effective. Even if I want to write Acceptance test cases, RSpec + Capybara will do.
I will illustrate how to do testing in Rails using RSpec in this article, with some of my experience of developing. Let's start.
1. Get to know some basic concepts in testing using RSpec
1.1 Why use FactoryGirl?
Fixture inside the Rails framework has some cons. Mainly they are:
- Only one set of fixtures in a default Rails application.
- Each model has its own fixture file, and not easy to do data association.
- Not flexible. Static data
So we should use factories to replace fixtures. FactoryGirl is the gem we are looking for. And it is from thoughtbot.
I will not talk about how to use FactoryGirl here. You just need to know that it can create object, with association. For example, if you are developing a blo g system, a User object has many Blogs, if you are still using fixture, then
user.blogs may return nothing.
1.2 What is mock and stub?
Stub is a fake object that returns a predetermined value for a method call without calling the actual object. To define a stub, the code may look like this:
Mock is similar to a stub, but in addition to returning the fake value, a mock object also sets a testable expectation that the method being replaced will actually be called in the test. If the method is not called, the mock obkect triggers a test failure. The code may look like this:
Difference here is that in mock, the
:name must be called to pass the test.
Mock and Stub in RSpec
Mock and stub have some different meaning in RSpec. Let's see the official definition
mock_modelmethod generates a test double that acts like an instance of
stub_modelgenerates an instance of a real object of
ActiveModel. The benefit of
mock_modelis that it can mock some object that may not exist
. If you're working on a controller spec and you need a model that doesn't exist, you can passmock_model` a string and the generated obkect will act as though its an instance of the class named by that string.
-- RSpec doc
mock_model, there is a piece of sample code
Various of specs will be discussed from highest level to the lower.
2. Feature Test
What does it test?
Feature specs are high-level tests meant to exercise slices of functionality through an application. They should drive the application only via its external interface, usually web pages.
--- RSpec doc
From RSpec 2.0, capybara can only be used inside feature test. DSL
scenario will be used here. So at the time of writing, feature test is the only place you can simulate user interactions with the browser.
The logic here is simple. You can start to feel the style RSpec doing test, and ignore the syntax details for now.
background is equavalent to the setup in other testing frameworks. Here I used FactoryGirl to create a sample user object.
click_button are all from capybara DSL. Since the scenarios we will test below are all require user to log in, we do the login process inside the background code block.
The scenario here is for a logged in user to change password. It visit the change password url, and fill in the form, then click "Update" button. The website will redirect user to another page. But this page should display something like "success", such as "You have successfully changed your password".
3. Request Test
What does it test?
To be simple, if you want to test a function(I mean the function of your product, not function in programming) that needs the collaboration of multiple controllers, or in another word, sends multiple request, you should use request test.
Unlike feature test, you cannot check whether the webpage contains certain piece of string. But you should check whether the action redirects to certain path, or renders certain template.
4. Controller Test
What does it test?
Controller test allows only one request to the controller. So in most situation, it refers to call only one action in the controller. And like request test, you can test the rendering, redirecting, variable values when being rendered, and http response code.
Let's test visiting a blog page. This page will show the content of the blog.
I first defines the
mock_blog object using FactoryGirl. Then in the
before block, I state no matter what parameter is, the
Blog.find() will always return
mock_blog during the testing. See how stub works? Then comes the real testing. The test sends a get request to the show page of
assigns(:blog) will get the
@blog in the template being rendered. I assert that this
@blog should be
Blog.find will return
5. Model Test
What does it test?
Model test is what we called "Unit test". It is the simplest type of testing. In Rails, it will test the ActiveModel classes.
Check out the example from RSpec official:
6. Other tests
Other tests are almost the same as Model test, and easy to understand, so I won't talk about it here. They are:
- View test: test content of rendered template when given the model
- Helper test: test helpers methods. Very much like Model test
- Mailer test: test the mail sent
- Routing test: test whether a route is reachable
There is one more test exists but has nothing to do with RSpec. It is the performance test.