cult3

Writing Functional Tests in Ruby on Rails

Mar 16, 2016

Table of contents:

  1. How are Functional Tests different to Unit Tests?
  2. What should you test for in a Functional Test?
  3. Writing the first Functional Controller Test
  4. Writing Functional Tests for failing requests
  5. Writing Functional Tests for successful requests
  6. Conclusion

So far in our exploration of Ruby on Rails we’ve looked at Unit Testing a couple of times. When you are first getting to grips with writing tests, you are probably going to start with Unit Testing.

Last week we briefly touched upon Functional Testing in Testing Active Job and Active Mailer in Ruby on Rails.

Functional Testing is trickier to get your head around because there is inevitably more going on each test, but these kinds of tests are probably more important than Unit Tests as they test that the many components of your application are working together correctly.

In today’s tutorial we’re going to be looking at Functional Testing in Rails, how Functional Tests are different than Unit Tests, and how to write Functional Tests that you can be confident in.

How are Functional Tests different to Unit Tests?

Writing tests for an application can seem difficult when you are first getting to grips with the subject, but in essence it’s really quite simple.

You basically need to test your application at different levels of abstraction to assert that the code you are writing is meeting the functional specification that you require.

A Unit Test should test the most basic “unit” of code in isolation with controlled inputs.

So for example, when testing a model, you instantiate the object directly and then call methods and pass it data to assert that you are returned the correct responses.

A Functional Test on the other hand works by making a request to a Controller action and then asserting that the request is handled correctly.

This will typically involve interacting with multiple components of your application such as Mailers, Queues, and the Database.

In the case of a Functional Test, you don’t need to be concerned with the details of each of these components. Instead you should be asserting that these components are working in unison to handle the request and return the correct response.

So hopefully you can see that the difference between the two types of tests is the level of abstraction that you are testing against.

What should you test for in a Functional Test?

These types of questions are difficult to get answers for because every person you ask will probably have a slightly different answer.

This can be frustrating if you are just starting out with Functional Tests because as a newbie it’s attractive to follow rules and best practices that have been discovered by the people who came before you.

My general rule is that I try to only test at this level of abstraction and not any deeper.

For example, if I’m testing a Controller that is creating a new object, I assert that the response is correct, but I don’t start reaching into the database to ensure every related record was created as it should been.

When I find myself writing these types of assertions I feel like if I’m worrying enough to make these checks, perhaps I need to write a Unit Test for this functionality.

So what do you test in a Functional Test?

The first thing I will typically assert for is the fact that the request was successful. This usually means checking the HTTP status code.

If it’s an API type request, I will usually check to make sure that the response includes the correct data.

If it’s a typical Controller action that returns a View, I will sometimes check to make sure the data has been made available. I usually tend to stay away from asserting stuff is in the DOM of the View though as I find these types of tests very brittle.

I will also test for things that are the concern of the Controller such as authentication or that session or flash data has been set correctly.

Writing the first Functional Controller Test

So now that you know the difference between Unit Tests and Functional Tests, and you have an idea of what we should be testing in a Functional Test, let’s take a look at a practical example.

The first Functional Test we’ll be writing will be for a RegisterController that is responsible for registering new users.

In today’s tutorial we’re going to be concentrating on writing the Functional Test, but we’re not going to be worrying about making it pass just yet. We’ll be looking at this process as a whole in next week’s tutorial.

So the first thing to do will be to create a new file under the test/controllers directory called register_controller_test.rb:

class RegisterControllerTest < ActionController::TestCase
end

As you can see, Controller tests should extend from ActionController::TestCase.

The first test we’re going to write will be asserting that the form to register as a new user is returned correctly:

test 'should get register form' do
end

This test should simulate making a GET request to the new method of the RegisterController.

The response from this method will simply be an HTML form, and so we only need to assert that the response was successful.

To make a GET request we can call the get method and pass it the name of the method as a symbol (What are Symbols in Ruby?):

get :new

To assert that the response is successful we can use the assert_response method and pass it a symbol of :success:

assert_response :success

Here is that method in full:

test 'should get register form' do
  get :new

  assert_response :success
end

As you can see, this is fairly simple as there’s not a lot going on in this request response cycle.

Writing Functional Tests for failing requests

The test we wrote in the last section was really simple. When the browser requests the registration form, it’s either going to return successfully or not. If the page does not return a successful response there’s probably a big problem with the application.

Testing GET requests is really simple because there is only ever one path.

But testing POST or PUT/PATCH requests is a bit more tricky because there is typically more than one path that the request could take.

For example, if you try to create a new resource with a POST request, the request could fail because you did not provide the required data.

In a typical web application, the user will be redirected back to the form so they can correct their mistake.

So to ensure that this functionality is working correctly, we can write a test:

test 'should fail to register with invalid data' do
  post :create, register: { email: '', username: '', password: '' }

  assert_response 400
end

In this test I’m making a POST request to the register method of the Controller.

The post helper method accepts a second parameter of a hash of request parameters. In this example it allows you to simulate submitting a form with data.

Finally I’m asserting that the HTTP Status code in the response is 400 for Bad Request.

Writing Functional Tests for successful requests

Finally we can write a test for the happy path of registering as a new user:

test 'should register new user via html request' do
  post :create, register: attributes_for(:user)

  assert_response 302
  assert_enqueued_jobs 1
end

In this example I’m sending valid data for a new user by using attributes_for helper method from Factory Girl (Replacing Fixtures with Factory Girl in Ruby on Rails). This will create a hash of valid parameters to create a new user.

The typical response from a Rails application on the successful operation of creating a new model is to redirect the user to another page. This will return the 302 HTTP Status Code and so we can assert that the response is correct.

I’m also going to be sending a welcome email as we saw in Getting started with Action Mailer in Ruby on Rails. This means I can use the assert_enqueued_jobs helper method that we looked at in Testing Active Job and Active Mailer in Ruby on Rails to assert that the Job was queued correctly.

Conclusion

In today’s tutorial we have looked at Functional Testing, how it’s different to Unit Testing, what you should test in a Functional Test, and how to write basic Functional Tests for common operations such as displaying a page, or creating a new resource.

We’ve only really scratched the surface of Functional Testing in Ruby on Rails in today’s tutorial, but it’s important to walk before you can run.

In next week’s tutorial we’re going to be looking at a fully fleshed out example of creating the functionality to register a new user for an application.

This will touch upon many of the things we have looked at over the past couple of weeks to see how the various components interact together as part of a cohesive whole.

If you’re still struggling to put the pieces together, hopefully next week’s tutorial will help you see how everything falls into place.

Philip Brown

@philipbrown

© Yellow Flag Ltd 2024.