class: middle, center # Testing Controllers [http://pjb3.me/bewd-testing-controllers](http://pjb3.me/bewd-testing-controllers) .footnote[ created with [remark](http://github.com/gnab/remark) ] --- # Functional Tests Functional tests are like unit tests for your controllers. They allow you to call an action and verify that it does what you expect. Let's create a few functional tests for the subscriptions controller that we generated when we first created our Rails app. Put the following code into `test/controllers/subscriptions_controller_test.rb`: ```ruby require 'test_helper' class SubscriptionsControllerTest < ActionController::TestCase def test_new get :new assert_response :success end end ``` If you run `rake`, you should see your test pass. Also, you can run just this specific test with the following command: .small[ .terminal $ ruby -I test test/controllers/subscriptions_controller_test.rb Run options: --seed 30934 # Running tests: . Finished tests in 0.185548s, 5.3894 tests/s, 5.3894 assertions/s. 1 tests, 1 assertions, 0 failures, 0 errors, 0 skips ] --- # Functional Tests This test doesn't do very much, but it is a start. First, since our test is named `SubscriptionControllerTest` Rails knows that we are working with the `SubscriptionsController`. Then in our test, this line: ```ruby get :new ``` Means to call the new action with a fake HTTP Get request with no parameters. In functional tests, we bypass the routing, we are calling the controller actions directly. The next line in our test looks like this: ```ruby assert_response :success ``` This means to verify that the response code is a 200. This means that a page was successfully returned, but we haven't verified anything about the page. --- # The response body When we call an action in a test, the action is executed and the view is also rendered. If we would like to see the HTML, we can print it out. Add this line in the test after the `get :new` line: ```ruby puts response.body ``` Now run your test again, using the method to run just this test. You will see the HTML that is generated by the view in the output. This is the same code you would see in your browser if you did view source on the page, if this were a real page that you loaded in your browser. --- # Assert Select Since the whole HTML document is available to us, we can verify a little more about the response to make sure it is working as expected. The best way to figure out what to verify in a test is to thing about the most important elements on the page. In this case, there should an input field to enter your email address within a form that has a button to subscribe. After the `assert_response :success` assertion, add these assertions: ```ruby assert_select "form[action=/subscriptions]" assert_select "input[name='subscription[email]']" assert_select "input[type=submit][value=Sign Up]" ``` `assert_select` takes a CSS selector and passes if there is an element that matches the selector and fails if there is not. If you are not familiar with this type of CSS selector, you can select any element that has an attribute with a certain name and value in the format of `tag_name[attribute_name=attribute_value]`. When the value contains special characters like brackets you have to wrap the value in single quotes, otherwise you can omit them and just use the value. --- # Testing the submit action So now that we have verified that we can show the subscription form, let's move on to verifying that when we submit the form it creates a subscription for the email address. Create another method in the same test that looks like this: ```ruby def test_create assert_difference 'Subscription.count' do post :create, subscription: { email: 'test@example.com' } end subscription = Subscription.last assert_equal 'test@example.com', subscription.email assert_redirected_to subscription_path(subscription) assert_equal 'Subscription was successfully created.', flash[:notice] end ``` Run your test again and make sure that it passes. There a several things going on in this test, so we will break down each part. --- # Assert Difference ```ruby assert_difference 'Subscription.count' do post :create, subscription: { email: 'test@example.com' } end ``` `assert_difference` is a method that accepts code in the form of a string and a block. The code in the string is evaluated once and the return value is saved. Then the block is evaluated. Then the code in the string is evaluated again. The assertion passes if the second time the code is evaluated it returns a number that is 1 greater than the value returned the first time the code was evaluated. The purpose of assert difference is to check that when the code is executed, the number of subscriptions increases by one. --- # Passing Parameters To The Action ```ruby assert_difference 'Subscription.count' do post :create, subscription: { email: 'test@example.com' } end ``` In the block, we have the code to call the create action. This time we are calling it with an HTTP POST request. We are also passing some parameters along. We specific the parameters in a Hash, which represents the same thing the form data would be converted into for us by Rails when we actually submit the form via the browser. Since the form as a parameter named `subscription[email]`, Rails will turn that into a Hash with `:subscription` as the key and a Hash for the value, with `:email` as a key in that Hash and the email address in a String as the value. When using `get` or `post` or [any of other HTTP methods][methods] in a functional test, the first argument is the name of the action and the second argument is a Hash of what you want the parameters to be. [methods]: http://api.rubyonrails.org/classes/ActionController/TestCase/Behavior.html#method-i-delete --- # Retrieving The Object Created ```ruby subscription = Subscription.last assert_equal 'test@example.com', subscription.email ``` In the next line of code, we simply call `Subscription.last` to get the last subscription that was created, which must be the one created in the previous line. Then we have a reference to the active record object so we can do some basic assertions on it like making sure the email address that we passed in via the parameters to the action was actually stored in the record that was saved. --- # Assert Redirected To ```ruby assert_redirected_to subscription_path(subscription) ``` Next we use the [assert_redirected_to][assert_redirected_to] method to verify that the action redirected us back to the expected place, which is the subscription show page. [assert_redirected_to]: http://api.rubyonrails.org/classes/ActionDispatch/Assertions/ResponseAssertions.html#method-i-assert_redirected_to --- # Assert Flash ```ruby assert_equal 'Subscription was successfully created.', flash[:notice] ``` In our last assertion in the test, we can see that we have access to the flash instance that was modified by our controller. In our `redirect_to` call in the action, we set a flash notice. In this assertion, we are verifying that the flash has the expected value, so that presumably the next action will show that message on the screen. --- # Functional Tests Are Brittle And Not Comprehensive So with just a couple of tests in our functional test for the subscriptions controller, we've verified quite a lot, but there are a view problems with testing this way. First, we aren't testing the routes at all. The routes could be completely wrong and this test would still pass. Second, in our test for the new action, we are asserting a lot of very specific details about the way HTML structured. For example, we are looking for an `input` tag of type `submit`. Someone might want to change that to a `button` tag. If they did, the page would still work as expected in the browser, but our tests would fail. This means we would have to change our test to get them to pass again, even though the code still works. This is referred to as a brittle test. Third, functional tests only allow us to test a single action at a time. We are forced to make assumptions about what the previous and next action will do in order to test this way. For example, the test for the create action specifies what the names of the parameters will be, even though it is actually the names of the form fields generated by the new action that controls that. Also, even if the create action sets the flash message as we expect, that doesn't mean the next page will show the message. Functional tests are good for testing controllers that are very isolated and do not depend on other actions. A good example of this are API actions that just receive and send JSON data. But for multiple actions that are related together in one logical flow from the perspective of the user, integrations tests are a better way to verify the application works as expected. --- # Integration Test Let's redo this test as an integration test. Put the following code into `test/integration/create_subscription_test.rb`: ```ruby require 'test_helper' class CreateSubscriptionTest < ActionDispatch::IntegrationTest def test_create_subscription get "/" assert_response :success end end ``` Make sure the test runs and passes. So far, our test looks very similar to the test for the new action in our functional test. The only difference so far is that we use the path to call, rather than the name of the action. This is because integration tests go through the routes, so that is an improvement. But unfortunately, out of the box Rails doesn't give us much else to work with in our integration tests. So before we go any further, we are going to add a gem to our project to help us out. --- # Capybara [Capybara](http://jnicklas.github.io/capybara/) is library that, as you will see, adds a bunch of helpful methods to your integration tests. To install Capybara, first add it to the test group in your `Gemfile`: ```ruby gem 'capybara' ``` Then, to enable it, add the following lines to the end of `test/test_helper.rb`: ```ruby require 'capybara/rails' class ActionDispatch::IntegrationTest include Capybara::DSL end ``` --- # Using Capybara Now modify the integration test we previous created to look like this: ```ruby def test_create_subscription visit "/" assert page.has_content?("Find out when we go live") end ``` This is again pretty similar to what we have, but verify that this test passes before we move on to see what else we can do with Capybara. --- # The Capybara DSL The idea behind Capybara is that you tests should read pretty close to how people would describe how to test a particular part of the application in English. Here's what it looks like for testing the new and the create action all at once: ```ruby def test_create_subscription visit "/" fill_in "Find out when we go live", with: 'test@example.com' click_on "Sign Up" assert page.has_content?('Subscription was successfully created.') assert_equal 'test@example.com', Subscription.last.email end ``` Run the test and it should pass. This is a better way to test our code because it uses the HTML from the first request to allow us to virtually fill out the form the way a user would. We can test more code, more accurately, with less code by using integration tests and Capybara. But how does it work? --- # How Capybara Works It may not be clear at first how Capybara works. The key steps of the test are: ```ruby visit "/" fill_in "Find out when we go live", with: 'test@example.com' click_on "Sign Up" assert page.has_content?('Subscription was successfully created.') ``` On the first line, the test will make an HTTP GET request to the root path and will get back an HTML document with a form in it. The next method used is `fill_in`, which will find a form field on the page which has a label that matches the text. The second argument to the method is how we provide what value should be "entered" into that field. Next, the `click_on` method looks for a button on the page with the text "Sign Up". Capybara detects that that button is a submit button, so it finds the form that the button is associated to and builds up an HTTP request to submit, just the way a browser would. Capybara then makes that request, which changes the page to be the page that the action redirects to. That page then displays the flash message, so the next assertion passes. Capybara does everything it can to mimic the browser and because you code actually runs in a browser, it ends up being a very good way to simulate how users interact with your application. --- # Debugging with Capybara Sometimes it can be confusing to figure out what is going on when your test doesn't pass because you can't actually see the page in a browser. Luckily Capybara has a way to simulate that for us as well. To take advantage of this feature, add the gem `launchy` to the test group in your Gemfile and run `bundle`. Then in your integration test, add this line directly after the `click_on` line: ```ruby save_and_open_page ``` When you run your test, you will see that Capybara will generate an HTML page and open it in a browser for you. You will not be able to see any images or styling in that page because it is just a static HTML page saved into your temp directory. It is not being served by a server, so none of the relative links for image, CSS or JavaScript will work. But that's ok, you usually just want to see the state of the HTML page at the point when you are trying to debug things, so this feature is still very useful. Make sure to take the `save_and_open_page` methods out of your tests before you commit them. It slows down your test suite considerably to run these methods unnecessarily, but it is very helpful to add them in temporarily when you need them to help understand what is happening when your test is running. --- # Exercise Write an integration test for the product admin interface that does the following things: 1. Go to /admin/products 2. Click on new product 3. Fill in the form with basic product information 4. Verify the "product was created" flash message appears 5. Click on the link to that new product 6. Click on the link to edit that product 7. Fill in the form to change the price of the product. 8. Verify the price of the product was updated. 9. Verify the "product was updated" flash message appears 10. Click on the product again 11. Click on the link to delete the product. 12. Verify the product no longer appears in the product list **Extra Credit:** Although you can get objects out of the database during an integration test to verify the state of things, it's generally considered bad form to do so. Make all of your assertions verify the data as it appears in the page to confirm changes are being made. Refer to the [Capybara Documentation][capybara-docs] to find out how to use some of the more advanced feature of Capybara to do this. [capybara-docs]: http://rubydoc.info/gems/capybara