ryanb / cancan

Authorization Gem for Ruby on Rails.
MIT License
6.27k stars 783 forks source link

In rspec - calling 2 actions on a controller in the same test can lead to empty model instance #104

Closed kyusik closed 14 years ago

kyusik commented 14 years ago

Let's say you have an rpsec test where you call the create action on a controller, then, in the same test, you call destroy for that instance you just created. If you are using load_and_authorize_resource in that controller, the second action will fail.

This is because when the create action is called, load_and_authorize_resource loads an "empty" model instance (i.e. an instance of MyModel with all attributes = nil), and that model instance is still assigned to @my_model for that controller when the second action is called. Then, when the delete action is called, ControllerResource#find just returns that "empty" model instance, rather than executing base.find.

Note that this is only an issue in rspec tests in which 2 controller actions are called.

Some possible solutions off the top of my head (but posting here bc Im looking for input from others):

  1. Call controller.instance_variable_set("@my_model", nil) between the first and second action calls within the rspec test
  2. Just don't call 2 actions in the same test (but this may hinder my test automation in some cases)
  3. Make ControllerResource#find always execute the find...or execute the find if the model_instance has a nil id

Are there other, more elegant/correct options?

Thanks!

ryanb commented 14 years ago

I don't consider this a problem with CanCan. If you're calling two actions in a controller test, they should mimic two separate requests since Rails controllers are not designed to handle two actions in the same request.

Since all instance variables are shared between the action calls you'll have weird behavior in tests which doesn't represent real requests. This is not specific to CanCan, it's just that CanCan makes it more apparent because it does not reload the instance variable if it's already set. The last thing you want is a test which fails or passes when it shouldn't and doesn't mimic the real thing.

I highly recommend not calling two actions on the same controller instance. Rails controllers are not designed to work this way. If you need to call multiple actions you should do it at a higher integration testing level which properly re-instatiates the controller for each request.

Alternatively you can investigate doing a proper "reset" on a controller which clears out instance variables so nothing is shared across actions. But then you may as well create a separate controller instance.