1
votes

In teaching myself Ruby and Rails, I'm currently building a user system based closely on the Hartl tutorial; the main difference at this point is that I'm testing it with Rspec/Capybara rather than TestUnit. So far most things have been fairly straightforward, but I've been stuck on this one problem in testing password resets.

When I test password resets manually, they work exactly as expected. Hence I'm pretty sure the problem is somewhere between Rspec, Capybara, and FactoryGirl.

The spec code that keeps failing is when I want to visit the password forgotten link:

# password_resets_spec.rb
visit(edit_password_reset_path(spec_user.reset_token, email: spec_user.email))

It gives me:

ActionController::UrlGenerationError: No route matches {:action=>"edit", :controller=>"password_resets", :email=>"[email protected]", :format=>nil, :id=>nil} missing required keys: [:id]

Password resets are a (partial) resource, and their id is the user's reset_token.

spec_user is generated by FactoryGirl:

#password_resets_spec.rb
let(:spec_user){ FactoryGirl.create :user }

The relevant :user factory doesn't set reset_token but when I try it with one that does, the token does not get properly set by the reset form, and doesn't match the generated digest. Here is the code that sets both:

# user.rb
def create_reset_digest
  self.reset_token = User.new_token
  update_attribute(:reset_digest, User.digest(reset_token))
  update_attribute(:reset_sent_at, Time.zone.now)
end

I can bypass the above error by using a factory that does set reset_token, but then the token used for the route doesn't match the digest saved, and the next step in the test fails.

My conclusion so far is that I'm misunderstanding something about how virtual attributes are handled, as the token is virtual while the digest is saved to database. I suspect that the reset_token I get for my route in the spec is not the same one involved in generating the digest, somehow.

Any help understanding what's going here would be much appreciated! :)

1

1 Answers

1
votes

You are getting a route error because there is no value for the reset_token; your id in this case.

You can set / assign virtual attributes on factory created objects. Just pass the value to the factory:

let(:spec_user){ FactoryGirl.create :user,  reset_token: "some value"}

However, for your use case, I'm guessing that you also need the hashed value to be in the database. pasword_resets#edit probably does a lookup of the user record by hashing the incoming reset_token.

So, in your spec:

require 'spec_helper'

describe "PasswordResets" do
  describe "#edit" do
    it "looks up the user and provides a form to reset the password" do
      # Generate the token in advance, so you can set the digest on the user
      reset_token = User.new_token
      # Create your user with the digest
      spec_user = FactoryGirl.create(:user, reset_token: reset_token, reset_digest: User.digest(reset_token), reset_sent_at: Time.zone.now)
      # Now visit path with an actual value for reset_tokeb
      visit(edit_password_reset_path(spec_user.reset_token, email: spec_user.email))
    end
  end
end