Skip to content

Instantly share code, notes, and snippets.

@ptolemybarnes
Last active January 4, 2016 15:41
Show Gist options
  • Save ptolemybarnes/2dfda607b85d01e113b0 to your computer and use it in GitHub Desktop.
Save ptolemybarnes/2dfda607b85d01e113b0 to your computer and use it in GitHub Desktop.
Chitter Challenge: Note on creating Good helper methods for your tests

What's the problem with a helper method like the following?:

  def sign_in(email, password)
    visit '/sessions/new'
    fill_in :email, with: email
    fill_in :password, with: password
    click_button 'Sign in'
  end
  • It relies on you remembering the order of arguments.
  • It relies on a model existing in the database with the same email / password values that you give it.
  • It relies on you not making a typo when you pass that email and password string.

So many things could go wrong! Even worse, what happens if you decide to add some validations such that passwords have to have at least one number? Unless you'd already formatted your examples like that, you'd have to find every call to the #sign_in method and correct that password string. So how can we improve on this? Like so:

  def sign_in_as(user)
    visit '/sessions/new'
    fill_in :email, with: user.email
    fill_in :password, with: user.password
    click_button 'Sign in'
  end

Wow nice!!! This means that all we have to remember to do is pass in a user model that presumably exists in the db. We don't have to remember an order of arguments. We're not at risk of opaque errors from having incorrectly typed a password string. So we'd call such a method like:

it 'displays a welcome message on sign-in' do
  user = User.first # assuming we have created a user in our setup. Wouldn't it be cool if we could do "create :user"? Hmmm...*
  
  sign_in_as(user)
  
  expect(page).to have_content "Welcome, #{user.email}!"
end

What about a method like the following?

def sign_up(username = 'Big G',
              name = 'Andy Gout',
              email = 'example@test.com',
              password = 'P0tat0M0nkey123!',
              password_confirmation = 'P0tat0M0nkey123!')
    visit '/users/new'
    
    fill_in :username, with: username
    fill_in :name, with: name
    fill_in :email, with: email
    fill_in :password, with: password
    fill_in :password_confirmation, with: password_confirmation
    click_button 'Sign up'
  end

Ouch >_< There are so many ways this could go wrong. Refactor like so:

def sign_up_as(user)
    visit '/users/new'
    
    fill_in :username, with: user.username
    fill_in :name,     with: user.name
    fill_in :email,    with: user.email
    fill_in :password, with: user.password
    fill_in :password_confirmation, with: user.password_confirmation
    click_button 'Sign up'
  end

(p.s yes, it does hurt me that the last line doesn't like up with the previous four).

But wait, how can we can supply an instance of user with details for a user that hasn't signed up yet? Surely the whole point of a sign up method is that you pass it details that are not yet in the DB. True, but recall that User.new would create a model without saving it in the DB. So we could call the above method in the following way:

it 'displays a welcome message on sign up' do
  user = User.new(user_params) # Wouldn't it be cool if we could do "build :user"?.*
  
  sign_up_as(user)
  
  expect(page).to have_content "Welcome to Chitter, #{user.email}!"
end

(above I'm suggesting another helper method, "user_params", that would allow you to keep the knowledge of what values are needed to create a user in one place. If only there were some nice gem for doing this... *)

Lovely.

  • Ptolemy.

*https://github.com/thoughtbot/factory_girl

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment