I've created a sets of specs for a controller that I have (Photo) that is a nested resource of a User. The specs all pass but some of them fail when I introduce CanCan in the Photos contoller:
load_and_authorize_resource :user
load_and_authorize_resource :photo
Here's a failing test case from one of my specs:
describe "get 'show'" do
let(:photo) { Factory.stub(:photo) }
it "searches for the users photo" do
@user.photos.should_receive(:find).with(:photo.object_id);
get :show, :user_id => @user.id, :id => :photo.object_id;
end
context "when the photo doesn't exist" do
before do
@user.photos.stub(:find).with(:photo.object_id).and_raise(ActiveRecord::RecordNotFound);
get :show, :user_id =>开发者_开发技巧; @user.id, :id => :photo.object_id;
end
it "assigns photo to nil" do
assigns[:photo].should be_nil;
end
it "renders the '404' template" do
response.status.should eql 404;
response.should render_template("#{Rails.root.to_s}/public/404.html") end
end
end
...
end
I've set up the user at the top of the spec file:
login(:user)
before do
@user = controller.current_user # this could be any user.
User.stub!(:find).and_return(@user)
end
and the show method in the Photos controller looks like this:
def show
# @user = User.find(params[:user_id])
# @photo = @user.photos.find(params[:id])
end
Note that the action is blank because Cancan performs the finds required automatically. Before I introduced Cancan the two lines in the action weren't commented out.
In my test, when I test that the find method is called on a users photos for example, the test fails. Cancan must be using some other technique to find the users photos (other than @users.photos.find()). Would anyone know how to rewrite the above tests for a controller that users Cancan, i.e. to replace the find stubs with something that will work?
What I'm looking for ideally is some sample specs for a RESTful controller that uses Cancan. Even a spec for the show action would be useful.
Thanks for reading!
Eddieps - I'm using Rails 3.0.6, Cancan 1.6.4 and Devise 1.3.3
There's a technical term for this, but there's a style difference in testing where you can either write a test which defines the implementation, our a test that defines the result.
Code like this:
@user.photos.should_receive(:find).with(:photo.object_id);
Declares that the way that the photo should be fetched is using a find operation on the photos association. As this is not the way that cancan loads the photo object, this is causing you problems in your tests.
An alternative approach is to define the desired outcome and leave the implementation undefined in the test. This makes the tests less brittle, and opens up more opportunities for code refactoring.
assigns[:photo].should eq photo;
A downside of this approach is it requires the use of Factories (Factory.create
) instead of Fixtures (Factory.stub
), but in my opinion the more flexible testing style justifies it.
there is this answer: Rspec, CanCan and Devise, but i am not really sure I want to patch CanCan. I would much rather understand and spec out the mock in the right way. I know the key issue is that CanCan does not call Contact.new(params[:contact]). Instead it calls contact.attributes = params[:contact] later after it has applied some initial attributes based on the current ability permissions. How to work around that? That is what I'm working now.
精彩评论