aboutsummaryrefslogtreecommitdiffstats
path: root/guides/source/testing.md
diff options
context:
space:
mode:
Diffstat (limited to 'guides/source/testing.md')
-rw-r--r--guides/source/testing.md180
1 files changed, 90 insertions, 90 deletions
diff --git a/guides/source/testing.md b/guides/source/testing.md
index 4faf59fad8..7b8a366192 100644
--- a/guides/source/testing.md
+++ b/guides/source/testing.md
@@ -31,11 +31,11 @@ h4. Rails Sets up for Testing from the Word Go
Rails creates a +test+ folder for you as soon as you create a Rails project using +rails new+ _application_name_. If you list the contents of this folder then you shall see:
-<shell>
+```shell
$ ls -F test
fixtures/ functional/ integration/ performance/ test_helper.rb unit/
-</shell>
+```
The +unit+ directory is meant to hold tests for your models, the +functional+ directory is meant to hold tests for your controllers, the +integration+ directory is meant to hold tests that involve any number of controllers interacting, and the +performance+ directory is meant for performance tests.
@@ -59,7 +59,7 @@ YAML-formatted fixtures are a very human-friendly way to describe your sample da
Here's a sample YAML fixture file:
-<yaml>
+```yaml
# lo & behold! I am a YAML comment!
david:
name: David Heinemeier Hansson
@@ -70,7 +70,7 @@ steve:
name: Steve Ross Kellock
birthday: 1974-09-27
profession: guy with keyboard
-</yaml>
+```
Each fixture is given a name followed by an indented list of colon-separated key/value pairs. Records are typically separated by a blank space. You can place comments in a fixture file by using the # character in the first column.
@@ -78,13 +78,13 @@ h5. ERB'in It Up
ERB allows you to embed Ruby code within templates. The YAML fixture format is pre-processed with ERB when Rails loads fixtures. This allows you to use Ruby to help you generate some sample data. For example, the following code generates a thousand users:
-<erb>
+```erb
<% 1000.times do |n| %>
user_<%= n %>:
username: <%= "user%03d" % n %>
email: <%= "user%03d@example.com" % n %>
<% end %>
-</erb>
+```
h5. Fixtures in Action
@@ -98,7 +98,7 @@ h5. Fixtures are ActiveRecord objects
Fixtures are instances of ActiveRecord. As mentioned in point #3 above, you can access the object directly because it is automatically setup as a local variable of the test case. For example:
-<ruby>
+```ruby
# this will return the User object for the fixture named david
users(:david)
@@ -107,7 +107,7 @@ users(:david).id
# one can also access methods available on the User class
email(david.girlfriend.email, david.location_tonight)
-</ruby>
+```
h3. Unit Testing your Models
@@ -119,18 +119,18 @@ NOTE: For more information on Rails <i>scaffolding</i>, refer to "Getting Starte
When you use +rails generate scaffold+, for a resource among other things it creates a test stub in the +test/unit+ folder:
-<shell>
+```shell
$ rails generate scaffold post title:string body:text
...
create app/models/post.rb
create test/unit/post_test.rb
create test/fixtures/posts.yml
...
-</shell>
+```
The default test stub in +test/unit/post_test.rb+ looks like this:
-<ruby>
+```ruby
require 'test_helper'
class PostTest < ActiveSupport::TestCase
@@ -139,19 +139,19 @@ class PostTest < ActiveSupport::TestCase
assert true
end
end
-</ruby>
+```
A line by line examination of this file will help get you oriented to Rails testing code and terminology.
-<ruby>
+```ruby
require 'test_helper'
-</ruby>
+```
As you know by now, +test_helper.rb+ specifies the default configuration to run our tests. This is included with all the tests, so any methods added to this file are available to all your tests.
-<ruby>
+```ruby
class PostTest < ActiveSupport::TestCase
-</ruby>
+```
The +PostTest+ class defines a _test case_ because it inherits from +ActiveSupport::TestCase+. +PostTest+ thus has all the methods available from +ActiveSupport::TestCase+. You'll see those methods a little later in this guide.
@@ -159,27 +159,27 @@ Any method defined within a +Test::Unit+ test case that begins with +test+ (case
Rails adds a +test+ method that takes a test name and a block. It generates a normal +Test::Unit+ test with method names prefixed with +test_+. So,
-<ruby>
+```ruby
test "the truth" do
assert true
end
-</ruby>
+```
acts as if you had written
-<ruby>
+```ruby
def test_the_truth
assert true
end
-</ruby>
+```
only the +test+ macro allows a more readable test name. You can still use regular method definitions though.
NOTE: The method name is generated by replacing spaces with underscores. The result does not need to be a valid Ruby identifier though, the name may contain punctuation characters etc. That's because in Ruby technically any string may be a method name. Odd ones need +define_method+ and +send+ calls, but formally there's no restriction.
-<ruby>
+```ruby
assert true
-</ruby>
+```
This line of code is called an _assertion_. An assertion is a line of code that evaluates an object (or expression) for expected results. For example, an assertion can check:
@@ -194,11 +194,11 @@ h4. Preparing your Application for Testing
Before you can run your tests, you need to ensure that the test database structure is current. For this you can use the following rake commands:
-<shell>
+```shell
$ rake db:migrate
...
$ rake db:test:load
-</shell>
+```
The +rake db:migrate+ above runs any pending migrations on the _development_ environment and updates +db/schema.rb+. The +rake db:test:load+ recreates the test database from the current +db/schema.rb+. On subsequent attempts, it is a good idea to first run +db:test:prepare+, as it first checks for pending migrations and warns you appropriately.
@@ -219,7 +219,7 @@ h4. Running Tests
Running a test is as simple as invoking the file containing the test cases through Ruby:
-<shell>
+```shell
$ ruby -Itest test/unit/post_test.rb
Loaded suite unit/post_test
@@ -228,13 +228,13 @@ Started
Finished in 0.023513 seconds.
1 tests, 1 assertions, 0 failures, 0 errors
-</shell>
+```
This will run all the test methods from the test case. Note that +test_helper.rb+ is in the +test+ directory, hence this directory needs to be added to the load path using the +-I+ switch.
You can also run a particular test method from the test case by using the +-n+ switch with the +test method name+.
-<shell>
+```shell
$ ruby -Itest test/unit/post_test.rb -n test_the_truth
Loaded suite unit/post_test
@@ -243,22 +243,22 @@ Started
Finished in 0.023513 seconds.
1 tests, 1 assertions, 0 failures, 0 errors
-</shell>
+```
The +.+ (dot) above indicates a passing test. When a test fails you see an +F+; when a test throws an error you see an +E+ in its place. The last line of the output is the summary.
To see how a test failure is reported, you can add a failing test to the +post_test.rb+ test case.
-<ruby>
+```ruby
test "should not save post without title" do
post = Post.new
assert !post.save
end
-</ruby>
+```
Let us run this newly added test.
-<shell>
+```shell
$ ruby unit/post_test.rb -n test_should_not_save_post_without_title
Loaded suite -e
Started
@@ -270,37 +270,37 @@ test_should_not_save_post_without_title(PostTest) [/test/unit/post_test.rb:6]:
<false> is not true.
1 tests, 1 assertions, 1 failures, 0 errors
-</shell>
+```
In the output, +F+ denotes a failure. You can see the corresponding trace shown under +1)+ along with the name of the failing test. The next few lines contain the stack trace followed by a message which mentions the actual value and the expected value by the assertion. The default assertion messages provide just enough information to help pinpoint the error. To make the assertion failure message more readable, every assertion provides an optional message parameter, as shown here:
-<ruby>
+```ruby
test "should not save post without title" do
post = Post.new
assert !post.save, "Saved the post without a title"
end
-</ruby>
+```
Running this test shows the friendlier assertion message:
-<shell>
+```shell
1) Failure:
test_should_not_save_post_without_title(PostTest) [/test/unit/post_test.rb:6]:
Saved the post without a title.
<false> is not true.
-</shell>
+```
Now to get this test to pass we can add a model level validation for the _title_ field.
-<ruby>
+```ruby
class Post < ActiveRecord::Base
validates :title, :presence => true
end
-</ruby>
+```
Now the test should pass. Let us verify by running the test again:
-<shell>
+```shell
$ ruby unit/post_test.rb -n test_should_not_save_post_without_title
Loaded suite unit/post_test
Started
@@ -308,7 +308,7 @@ Started
Finished in 0.193608 seconds.
1 tests, 1 assertions, 0 failures, 0 errors
-</shell>
+```
Now, if you noticed, we first wrote a test which fails for a desired functionality, then we wrote some code which adds the functionality and finally we ensured that our test passes. This approach to software development is referred to as _Test-Driven Development_ (TDD).
@@ -316,17 +316,17 @@ TIP: Many Rails developers practice _Test-Driven Development_ (TDD). This is an
To see how an error gets reported, here's a test containing an error:
-<ruby>
+```ruby
test "should report error" do
# some_undefined_variable is not defined elsewhere in the test case
some_undefined_variable
assert true
end
-</ruby>
+```
Now you can see even more output in the console from running the tests:
-<shell>
+```shell
$ ruby unit/post_test.rb -n test_should_report_error
Loaded suite -e
Started
@@ -339,7 +339,7 @@ NameError: undefined local variable or method `some_undefined_variable' for #<Po
/test/unit/post_test.rb:6:in `test_should_report_error'
1 tests, 0 assertions, 0 failures, 1 errors
-</shell>
+```
Notice the 'E' in the output. It denotes a test with error.
@@ -416,13 +416,13 @@ Now that we have used Rails scaffold generator for our +Post+ resource, it has a
Let me take you through one such test, +test_should_get_index+ from the file +posts_controller_test.rb+.
-<ruby>
+```ruby
test "should get index" do
get :index
assert_response :success
assert_not_nil assigns(:posts)
end
-</ruby>
+```
In the +test_should_get_index+ test, Rails simulates a request on the action called +index+, making sure the request was successful and also ensuring that it assigns a valid +posts+ instance variable.
@@ -435,21 +435,21 @@ The +get+ method kicks off the web request and populates the results into the re
Example: Calling the +:show+ action, passing an +id+ of 12 as the +params+ and setting a +user_id+ of 5 in the session:
-<ruby>
+```ruby
get(:show, {'id' => "12"}, {'user_id' => 5})
-</ruby>
+```
Another example: Calling the +:view+ action, passing an +id+ of 12 as the +params+, this time with no session, but with a flash message.
-<ruby>
+```ruby
get(:view, {'id' => '12'}, nil, {'message' => 'booya!'})
-</ruby>
+```
NOTE: If you try running +test_should_create_post+ test from +posts_controller_test.rb+ it will fail on account of the newly added model level validation and rightly so.
Let us modify +test_should_create_post+ test in +posts_controller_test.rb+ so that all our test pass:
-<ruby>
+```ruby
test "should create post" do
assert_difference('Post.count') do
post :create, :post => { :title => 'Some title'}
@@ -457,7 +457,7 @@ test "should create post" do
assert_redirected_to post_path(assigns(:post))
end
-</ruby>
+```
Now you can try running all the tests and they should pass.
@@ -487,14 +487,14 @@ After a request has been made by using one of the 5 methods (+get+, +post+, etc.
As is the case with normal Hash objects, you can access the values by referencing the keys by string. You can also reference them by symbol name, except for +assigns+. For example:
-<ruby>
+```ruby
flash["gordon"] flash[:gordon]
session["shmession"] session[:shmession]
cookies["are_good_for_u"] cookies[:are_good_for_u]
# Because you can't use assigns[:something] for historical reasons:
assigns["something"] assigns(:something)
-</ruby>
+```
h4. Instance Variables Available
@@ -509,22 +509,22 @@ h4. Testing Templates and Layouts
If you want to make sure that the response rendered the correct template and layout, you can use the +assert_template+
method:
-<ruby>
+```ruby
test "index should render correct template and layout" do
get :index
assert_template :index
assert_template :layout => "layouts/application"
end
-</ruby>
+```
Note that you cannot test for template and layout at the same time, with one call to +assert_template+ method.
Also, for the +layout+ test, you can give a regular expression instead of a string, but using the string, makes
things clearer. On the other hand, you have to include the "layouts" directory name even if you save your layout
file in this standard layout directory. Hence,
-<ruby>
+```ruby
assert_template :layout => "application"
-</ruby>
+```
will not work.
@@ -533,12 +533,12 @@ Otherwise, assertion will fail.
Hence:
-<ruby>
+```ruby
test "new should render correct layout" do
get :new
assert_template :layout => "layouts/application", :partial => "_form"
end
-</ruby>
+```
is the correct way to assert for the layout when the view renders a partial with name +_form+. Omitting the +:partial+ key in your +assert_template+ call will complain.
@@ -546,7 +546,7 @@ h4. A Fuller Functional Test Example
Here's another example that uses +flash+, +assert_redirected_to+, and +assert_difference+:
-<ruby>
+```ruby
test "should create post" do
assert_difference('Post.count') do
post :create, :post => { :title => 'Hi', :body => 'This is my first post.'}
@@ -554,7 +554,7 @@ test "should create post" do
assert_redirected_to post_path(assigns(:post))
assert_equal 'Post was successfully created.', flash[:notice]
end
-</ruby>
+```
h4. Testing Views
@@ -570,21 +570,21 @@ There are two forms of +assert_select+:
For example, you could verify the contents on the title element in your response with:
-<ruby>
+```ruby
assert_select 'title', "Welcome to Rails Testing Guide"
-</ruby>
+```
You can also use nested +assert_select+ blocks. In this case the inner +assert_select+ runs the assertion on the complete collection of elements selected by the outer +assert_select+ block:
-<ruby>
+```ruby
assert_select 'ul.navigation' do
assert_select 'li.menu_item'
end
-</ruby>
+```
Alternatively the collection of elements selected by the outer +assert_select+ may be iterated through so that +assert_select+ may be called separately for each element. Suppose for example that the response contains two ordered lists, each with four list elements then the following tests will both pass.
-<ruby>
+```ruby
assert_select "ol" do |elements|
elements.each do |element|
assert_select element, "li", 4
@@ -594,7 +594,7 @@ end
assert_select "ol" do
assert_select "li", 8
end
-</ruby>
+```
The +assert_select+ assertion is quite powerful. For more advanced usage, refer to its "documentation":http://api.rubyonrails.org/classes/ActionDispatch/Assertions/SelectorAssertions.html.
@@ -609,11 +609,11 @@ There are more assertions that are primarily used in testing views:
Here's an example of using +assert_select_email+:
-<ruby>
+```ruby
assert_select_email do
assert_select 'small', 'Please click the "Unsubscribe" link if you want to opt-out.'
end
-</ruby>
+```
h3. Integration Testing
@@ -621,15 +621,15 @@ Integration tests are used to test the interaction among any number of controlle
Unlike Unit and Functional tests, integration tests have to be explicitly created under the 'test/integration' folder within your application. Rails provides a generator to create an integration test skeleton for you.
-<shell>
+```shell
$ rails generate integration_test user_flows
exists test/integration/
create test/integration/user_flows_test.rb
-</shell>
+```
Here's what a freshly-generated integration test looks like:
-<ruby>
+```ruby
require 'test_helper'
class UserFlowsTest < ActionDispatch::IntegrationTest
@@ -640,7 +640,7 @@ class UserFlowsTest < ActionDispatch::IntegrationTest
assert true
end
end
-</ruby>
+```
Integration tests inherit from +ActionDispatch::IntegrationTest+. This makes available some additional helpers to use in your integration tests. Also you need to explicitly include the fixtures to be made available to the test.
@@ -666,7 +666,7 @@ h4. Integration Testing Examples
A simple integration test that exercises multiple controllers:
-<ruby>
+```ruby
require 'test_helper'
class UserFlowsTest < ActionDispatch::IntegrationTest
@@ -688,13 +688,13 @@ class UserFlowsTest < ActionDispatch::IntegrationTest
assert assigns(:products)
end
end
-</ruby>
+```
As you can see the integration test involves multiple controllers and exercises the entire stack from database to dispatcher. In addition you can have multiple session instances open simultaneously in a test and extend those instances with assertion methods to create a very powerful testing DSL (domain-specific language) just for your application.
Here's an example of multiple sessions and custom DSL in an integration test
-<ruby>
+```ruby
require 'test_helper'
class UserFlowsTest < ActionDispatch::IntegrationTest
@@ -740,7 +740,7 @@ class UserFlowsTest < ActionDispatch::IntegrationTest
end
end
end
-</ruby>
+```
h3. Rake Tasks for Running your Tests
@@ -768,7 +768,7 @@ h3. Setup and Teardown
If you would like to run a block of code before the start of each test and another block of code after the end of each test you have two special callbacks for your rescue. Let's take note of this by looking at an example for our functional test in +Posts+ controller:
-<ruby>
+```ruby
require 'test_helper'
class PostsControllerTest < ActionController::TestCase
@@ -800,7 +800,7 @@ class PostsControllerTest < ActionController::TestCase
end
end
-</ruby>
+```
Above, the +setup+ method is called before each test and so +@post+ is available for each of the tests. Rails implements +setup+ and +teardown+ as +ActiveSupport::Callbacks+. Which essentially means you need not only use +setup+ and +teardown+ as methods in your tests. You could specify them by using:
@@ -811,7 +811,7 @@ Above, the +setup+ method is called before each test and so +@post+ is available
Let's see the earlier example by specifying +setup+ callback by specifying a method name as a symbol:
-<ruby>
+```ruby
require '../test_helper'
class PostsControllerTest < ActionController::TestCase
@@ -849,17 +849,17 @@ class PostsControllerTest < ActionController::TestCase
end
end
-</ruby>
+```
h3. Testing Routes
Like everything else in your Rails application, it is recommended that you test your routes. An example test for a route in the default +show+ action of +Posts+ controller above should look like:
-<ruby>
+```ruby
test "should route to post" do
assert_routing '/posts/1', { :controller => "posts", :action => "show", :id => "1" }
end
-</ruby>
+```
h3. Testing Your Mailers
@@ -893,7 +893,7 @@ h5. The Basic Test Case
Here's a unit test to test a mailer named +UserMailer+ whose action +invite+ is used to send an invitation to a friend. It is an adapted version of the base test created by the generator for an +invite+ action.
-<ruby>
+```ruby
require 'test_helper'
class UserMailerTest < ActionMailer::TestCase
@@ -909,19 +909,19 @@ class UserMailerTest < ActionMailer::TestCase
end
end
-</ruby>
+```
In this test, +@expected+ is an instance of +TMail::Mail+ that you can use in your tests. It is defined in +ActionMailer::TestCase+. The test above uses +@expected+ to construct an email, which it then asserts with email created by the custom mailer. The +invite+ fixture is the body of the email and is used as the sample content to assert against. The helper +read_fixture+ is used to read in the content from this file.
Here's the content of the +invite+ fixture:
-<pre>
+```
Hi friend@example.com,
You have been invited.
Cheers!
-</pre>
+```
This is the right time to understand a little more about writing tests for your mailers. The line +ActionMailer::Base.delivery_method = :test+ in +config/environments/test.rb+ sets the delivery method to test mode so that email will not actually be delivered (useful to avoid spamming your users while testing) but instead it will be appended to an array (+ActionMailer::Base.deliveries+).
@@ -931,7 +931,7 @@ h4. Functional Testing
Functional testing for mailers involves more than just checking that the email body, recipients and so forth are correct. In functional mail tests you call the mail deliver methods and check that the appropriate emails have been appended to the delivery list. It is fairly safe to assume that the deliver methods themselves do their job. You are probably more interested in whether your own business logic is sending emails when you expect them to go out. For example, you can check that the invite friend operation is sending an email appropriately:
-<ruby>
+```ruby
require 'test_helper'
class UserControllerTest < ActionController::TestCase
@@ -946,7 +946,7 @@ class UserControllerTest < ActionController::TestCase
assert_match(/Hi friend@example.com/, invite_email.body)
end
end
-</ruby>
+```
h3. Other Testing Approaches