25 Testing - getfretless/elevennote GitHub Wiki

TESTING

Maybe you've noticed by now, but there are a lot of things that can cause parts of your apps to not work (like, say, a stray comma or a typo).

Wouldn't it be nice if there were a way to make sure things keep working the way you expect them to?

Wouldn't it also be nice to have some idea of what is even expected to work?

I betcha Rails just might have something for that. That's right, Rails ships with a testing framework built in called Test::Unit. It may not be the fanciest, but it's dirt simple, and I like that.

When you are writing code that isn't particularly complicated, you probably have a pretty good idea of what you expect your code to do. Why not write just a little bit of extra code to verify that behavior, and document your intentions.

Because of the time constraints of the class, and the exploratory nature of the cool stuff we've been showing you, we haven't been writing tests like we normally would like to, and frankly, it makes us a little sad.

But there's no way we were going to let you go without stepping you through how to test some stuff.

Because we are writing web apps that people use through a browser, we think the highest value per line of code tests you can write are "integration" tests, and ideally, they run in an environment that actually drives a web browser, like a real user will.

Let's add some gems that we like for this. Open up Gemfile, and inside the :development, :test group, add this:

gem 'capybara'
gem 'selenium-webdriver'

and run bundle install

Capybara is a Ruby interface to web browsers that can be exercised programmatically.

Let's add some stuff from Capybara's Github README (and some other stuff) to the bottom of our test_helper file, so that we can use it's cool stuff in our integration tests. test/test_helper.rb

require 'capybara/rails'
#... other stuff
class ActionDispatch::IntegrationTest
  # Make the Capybara DSL available in all integration tests
  include Capybara::DSL

  Capybara.default_driver = :selenium

  def teardown
    Capybara.reset_sessions!
  end
end

Since we are testing an app that deals with users and notes, we should probably have at least one user and note.

Rails ships with fixtures as the default test data strategy, so let's go with that for now. The fixtures are YAML files, and can contain ERB if necessary. They also can relate to each other. When the test suite runs, all fixtures are loaded into the database at once, so it is nice and performant when running the whole suite (though may slow things down if running tests one at a time in a large suite).

Let's create a fixture for a user and a note that belongs to that user: test/fixtures/users.yml

dave:
  name: 'David Jones'
  username: 'dave'
  password_digest: <%= BCrypt::Password.create 'password' %>

test/fixtures/notes.yml

groceries:
  user: dave
  title: Groceries
  body_text: 'milk, eggs, cheese'
  body_html: '<ul><li>milk</li><li>eggs</li><li>cheese</li></ul>'

todo:
  user: dave
  title: 'Todo'
  body_text: 'Feed the dogs'
  body_html: '<p>Feed the dogs</p>'

Notice that there's a key user: dave specified in the YAML. That is a reference back to the user fixture with the top level key dave.

We also use BCrypt to make a password digest for the word password that we can use to log in in the test environment. We could have also just copy/pasted the digest itself, but this makes it more evident what the password actually is.

Let's create an empty test to make sure we have our environment setup correctly: test/integration/sessions_integration_test.rb

require 'test_helper'

class SessionsIntegrationTest < ActionDispatch::IntegrationTest
  test 'the truth, the whole truth, and nothing but the truth' do
    assert true
  end
end

if you run bin/rake test, or even just bin/rake, will run the test suite, and let us know if it worked out or not.

$ bin/rake

Here's a bunch of integration tests we've written to exercise this app. Let's take a look at them: test/integration/welcome_integration_test.rb

require 'test_helper'

class WelcomeIntegrationTest < ActionDispatch::IntegrationTest

  test 'The home page has the name of the app' do
    visit '/'
    assert page.has_content? 'ElevenNote'
  end

end

test/integration/sessions_integration_test.rb

require 'test_helper'

class SessionsIntegrationTest < ActionDispatch::IntegrationTest

  test 'Allows a guest to sign up' do
    visit '/'
    click_link 'Sign up!'
    fill_in 'user_name', with: 'David Jones' # using ID attribute of form element
    fill_in 'user[username]', with: 'unixmonkey' # using NAME attribute of form element
    fill_in 'user[password]', with: 'password'
    click_button 'Sign Up'
    assert page.has_content? 'Thanks for signing up!'
  end

  test 'Allows an existing user to log in' do
    login users(:dave)
    assert page.has_content? 'Welcome!'
  end

end

test/integration/notes_integration_test.rb

require 'test_helper'

class NotesIntegrationTest < ActionDispatch::IntegrationTest

  test 'Creating a note saves it' do
    login users(:dave)
    click_button 'New Note'
    fill_in 'note[title]', with: 'Awesome note'
    fill_in_richtext page, 'Lorem ipsum'
    click_button 'Create Note'
    assert page.has_content? 'Your note has been created'
    assert page.has_content? 'Lorem ipsum'
  end

  test 'Updating a note changes it' do
    login users(:dave)
    choose_note notes(:groceries)
    fill_in 'note[title]', with: 'Food'
    fill_in_richtext page, '<ul><li>Baloney</li></ul>'
    click_button 'Update Note'
  end

  test 'Deleting a note removes it' do
    login users(:dave)
    choose_note notes(:todo)
    within('form.edit_note') do
      page.accept_confirm do
        find('i.fa-trash-o').click
      end
    end
    assert page.has_content? 'note has been deleted'
  end

end

Also, we've extracted some test code that needs running in more than one test file, and placed it inside SessionsIntegrationTest in the test_helper.rb file (this is the full file for those of you cut and pasting): test/test_helper.rb

ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../../config/environment', __FILE__)
require 'rails/test_help'
require 'capybara/rails'

class ActiveSupport::TestCase
  # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
  fixtures :all

  # Add more helper methods to be used by all tests here...
end

class ActionDispatch::IntegrationTest
  # Make the Capybara DSL available in all integration tests
  include Capybara::DSL

  Capybara.default_driver = :selenium

  def teardown
    Capybara.reset_sessions!
    Capybara.use_default_driver
  end

  def login(user)
    visit '/'
    fill_in 'user[username]', with: user.username
    fill_in 'user[password]', with: 'password'
    click_button 'Sign In'
  end

  def logout
    click_link 'Logout' if page.has_content? 'Logout'
  end

  def fill_in_richtext(page, content)
    if Capybara.current_driver == :selenium
      page.execute_script "Bootsy.areas['note_body_html'].editor.setValue('#{content}');"
    else
      fill_in 'note[body_html]', with: content
    end
  end

  def choose_note(note)
    within('#notes') do
      list_items = all('#notes > li')
      list_item = list_items.detect { |li| li['data-url'].to_s.include?("/notes/#{note.id}") }
      list_item.click
    end
  end

end

Let's run these test and make sure they work.

$ bin/rake

Sometimes, it is helpful to do some debugging in our tests, but it can be hard when you can't actually see the test in progress. Capybara comes with some helpers we can drop in our code (in addition to the trusty binding.pry): save_and_open_page and save_and_open_screenshot. If you don't have launchy installed, you can just open tmp/capybara to preview and open the files those commands create.

⚠️ **GitHub.com Fallback** ⚠️