Skip to main content

Testing Rails Applications

Posted by bleonard on January 4, 2008 at 11:04 AM PST

All well developed applications are supported by tests. In this entry I extend the web log I've been building to include some unit, functional and integration tests.

Setting Things Up

I'm going to begin from where I left off in my previous post: An Introduction to using AJAX with Rails: Take 2. Alternatively, you can start from RubyWeblogAJAX.zip, which is the completed project from that post.

Test the RubyWeblog Project

  1. Open the rubyweblog project.
  2. Note, if you are starting with the provided rubyweblog project, you will also need to create the rubyweblog_development database and run the migrations.
  3. Run the project and browse to http://localhost:3000/blog to verify that it works.

The Plan

We're going to look at the facilities Rails provides for testing our model classes, controller classes and entire application.

Model Testing (Unit Tests)

The Rails framework uses unit tests for testing of the model classes. Back when we first created the Post model class, a couple of test files were created for us, test/unit/post_test.rb and test/fixtures/post.yml:



The post.yml file contains test data that's made available to the unit test and reloaded in the database every time the test is run. Don't worry, this isn't going to affect your development database, but rather the test database that's configured in database.yml, which we haven't created yet:



Create the rubyweblog_test Database

  1. Switch to the Services window and expand the Databases node. We'll use an existing MySQL connection to create the test database. If you don't already have an existing MySQL database connection, create one for the development database.
    1. Expand the Drivers node, right-click the MySQL driver and choose Connect Using
    2. Set the Database URL to: jdbc:mysql://localhost/rubyweblog_development
    3. Set the User Name to: root
    4. Leave the Password blank
    5. Click OK to establish the connection
  2. Right-click an existing MySQL connection (connect first if not already) and select Execute Command.
  3. Enter create database rubyweblog_test
  4. Right-click the commend and select Run Statement

Prepare the Database

We need to run the migrations against the test database. This is done using the rake task db:test:prepare

  1. Right-click the rubyweblog project and choose Run Rake Task > db > test > prepare

Run the Default Unit Test

The default unit test that was created for us when the model was generated is very basic and really only servers to prove our test environment is properly configured.

  1. Open post_test.rb
  2. Right-click the file and select Run File.







    If for some reason the test fails to run, the error messages are generally descriptive enough to alert you to the problem, like "Unknown database 'rubyweblog_test' if you forgot to create the test database first.

Test the Validations

The post model is pretty simple and doesn't offer a lot to test, but it does have a couple of validations when can test, so therefore, we can assert that an empty model is invalid:

  def test_invalid_with_empty_attributes
    post = Post.new
    assert !post.valid?                  # An empty Post model will be invalid
    assert post.errors.invalid?(:title)  # The title field will have validation errors
    assert post.errors.invalid?(:body)   # The body field will have validation errors
  end

Using Fixture Data

To give us something more to test, let's require blog titles be unique.

  1. Navigate to the tested class (Navigate > Go to Tested Class)
  2. Add validates_uniqueness_of :title
  3. Open fixtures/post.yml and add the following:
    first:
      id: 1
      title: George Jetson
      body: Was a man ahead of his time
  4. Navigate back to the post_test.rb and add the following test method:
      def test_unique_title
        post = Post.new(:title  => posts(:first).title, # Pull the title from the fixture
                        :body   => "Whatever")
        assert !post.save
        assert_equal "has already been taken", post.errors.on(:title)
      end

Run the Tests

There are several way to run the test:

  1. From the test file, right-click the file and choose Run File or Test File, as both do the same when executed from a test file.
  2. From the tested class, right-click the file and choose Test File.


Controller Testing (Functional Tests)

When the scaffolding was generated, Rails also generated a functional test stub in the test/functional directory, which has 8 tests for the actions created by the generator (index, list, show, new, create, edit, update, destroy).

  1. Open test/functional/blog_controller_test.rb.
  2. Right-click the file and choose Run File. You should see one failure:







    This failure is caused by the validations we've added to the Post model as this line from the test_create method doesn't pass any arguments in with the post:
        post :create, :post => {}
    (Note, the first "post" in the line above refers to the HTTP Post method, while ":post" refers to the Post model).


  3. Add required parameters to the empty :post hash:
        post :create, :post => { :title => "Astro", 
                                 :body  => "Was loved by George" }  

    All 8 tests and 25 assertions should now pass.

Testing the post_comment Action

The default tests provide excellent examples on how to create tests for other controller actions. I'll create a new test for the additional controller action we've created since the controller was generated, post_comment:

  def test_post_comment
    xhr :post,                                            # Simulate xml_http_request
      :post_comment,                                      # the action to call 
      { :comment => { :comment => "What about Jane?" }},  # the parameters to post
      nil,                                                # session variables expected by the action
      { :post_id => 100 }                                 # flash variables expected by the action
             
    assert_response :success
    assert_template '_comment'
    assert_equal 'Comment was successfully added.', flash[:notice]
    assert_equal 'What about Jane?', Comment.find_by_post_id(100).comment
  end 

Application Testing (Integration Tests)

Integration tests are used for testing application flow and generally test a scenario of events. The blog application is very simple, but we can still create a test scenario such as creating an entry, viewing the entry, editing the entry and adding a comment.

Integration tests are very much like functional tests, but since they can span multiple controllers you have to pass the controller as well as the action to the HTTP method (e.g., get "/blog/index"). Also, the Rails framework has not created a default integration test file for us, one must first be generated.

  1. Generate an Integration Test named ApplicationUsage.
  2. Add the following test method:
      def test_creating_a_post
       
        # Clear the database tables
        Comment.delete_all 
        Post.delete_all 
       
        # Load the home page
        get "/blog/index"
        assert_response :success
        assert_template "list"
       
        # Load the New post page
        get "/blog/new"
        assert_response :success
        assert_template "new"
       
        # Create a new blog post
        post "/blog/create", :post => { :title => "Integration Test Title",
                                        :body => "Integration Test Body"}
        assert_response :redirect
       
        # Get the post variable from the controller so we can fetch the generated id
        # which we'll use to get the record.
        post_id = @controller.instance_variable_get(:@post).attributes['id']
       
        # Show the newly created post
        get "/blog/show/#{post_id}"
        assert_response :success
        assert_template "show"
       
        # Edit the post   
        get "/blog/edit/#{post_id}"
        assert_response :success
        assert_template "edit"
       
        # Post the edits   
        post "/blog/update/#{post_id}", :post => { :title => "Integration Test Title Updated",
                                                   :body => "Integration Test Body Updated"}
        assert_response :redirect
       
        # Verify the post exists
        posts = Post.find(:all)
        assert_equal 1, posts.size
       
        # Confirm it's contents
        post = posts[0]   
        assert_equal  "Integration Test Title Updated", post.title
        assert_equal  "Integration Test Body Updated",  post.body
      end
  3. Run the file to execute the test.

You may have noticed that the test above does not test adding a comment. This is because I couldn't figure out how to set the flash from an integration test. Here's the signature for the xhr method I used from the functional test:
xhr(request_method, action, parameters = nil, session = nil, flash = nil)

and here's the signature for the xhr method for integration testing:
xhr(request_method, path, parameters = nil, headers = nil)

And I just don't see a way to set the flash. If anyone knows, please let me know.

Running All Tests

You can run the entire suite of tests by selecting Run > Test "rubyweblog" or right-clicking the project and selecting Test. You'll also see a nice summary at the bottom of the editor window (this summary is there for every test run - it's just more useful in this case because it shows the aggregate of all test runs).



The Completed Application

RubyWebLogTested.zip

References

Related Topics >>