92

I want to test controller's action and flash messages presence with rspec.

action:

def create
  user = Users::User.find_by_email(params[:email])
  if user
    user.send_reset_password_instructions
    flash[:success] = "Reset password instructions have been sent to #{user.email}."
  else
    flash[:alert] = "Can't find user with this email: #{params[:email]}"
  end

  redirect_to root_path
end

spec:

describe "#create" do
  it "sends reset password instructions if user exists" do
    post :create, email: "email@example.com"      
    expect(response).to redirect_to(root_path)
    expect(flash[:success]).to be_present
  end
...

But I've got an error:

Failure/Error: expect(flash[:success]).to be_present
   expected `nil.present?` to return true, got false
Mike Andrianov
  • 3,033
  • 3
  • 23
  • 24

5 Answers5

78

You are testing for the presence of flash[:success], but in your controller you are using flash[:notice]

rabusmar
  • 4,084
  • 1
  • 25
  • 29
  • Oh, sorry, I just slipped here. Same error with flash[:notice] – Mike Andrianov Jul 23 '14 at 20:23
  • 4
    In that case, the problem is probably with your controller code and/or test data. Try to change the `expect(flash[:notice])` to `expect(flash[:alert])`, and if the test passes then probably it's just that the test email doesn't exists. – rabusmar Jul 23 '14 at 20:32
54

The best way to test flash messages is provided by the shoulda gem.

Here are three examples:

expect(controller).to set_flash
expect(controller).to set_flash[:success]
expect(controller).to set_flash.now[:alert].to(/are not valid/)
Robin Daugherty
  • 7,115
  • 4
  • 45
  • 59
43

If you are more interested in the content of the flash messages you can use this:

expect(flash[:success]).to match(/Reset password instructions have been sent to .*/)

or

expect(flash[:alert]).to match(/Can't find user with this email: .*/)

I would advise against checking for a specific message unless that message is critical and/or it does not change often.

Mugur 'Bud' Chirica
  • 4,246
  • 1
  • 31
  • 34
5

With: gem 'shoulda-matchers', '~> 3.1'

The .now should be called directly on the set_flash.

Using set_flash with the now qualifier and specifying now after other qualifiers is no longer allowed.

You'll want to use now immediately after set_flash. For instance:

# Valid
should set_flash.now[:foo]
should set_flash.now[:foo].to('bar')

# Invalid
should set_flash[:foo].now
should set_flash[:foo].to('bar').now
killerkiara
  • 101
  • 3
  • 9
2

The another approach is to leave out the fact that a controller has flash messages and write an integration test instead. This way you increase chances that you will not need to alter the test once you decide to show that message using JavaScript or by some other way.

See also https://stackoverflow.com/a/13897912/2987689

Artur INTECH
  • 6,024
  • 2
  • 37
  • 34