Fixing Rails 3.2 tests with Mass Assignment Security errors
In my controller functional tests I typically start off with something like this:
tests/function/photos_controller_test.rb setup do @photo = Factory(:photo) end test "should create photo" do assert_difference 'Photo.count' do xhr :post, :create, photo: @photo.attributes, format: "json" end assert_response :success json = JSON.parse(@response.body) assert_equal Photo.last.id, json["id"] end
The setup method creates a valid Photo object (using the Factory Girl gem). The test checks that the controller can create and return a valid Photo by passing it the attributes of a valid Photo, and in this case using json. It's a fairly simple test.
When I upgraded to Rails 3.2, the upgrade guide tells you to update your test environment to "Raise exception on mass assignment protection for Active Record models" by setting "config.active_record.mass_assignment_sanitizer = :strict" in test.rb
So, when I ran my tests, I would see
ActiveModel::MassAssignmentSecurity::Error: Can't mass-assign protected attributes: id, created_at, updated_at
My tests are failing! Oh no! Looks like it isn't as easy as simply calling .attributes anymore.
Of course, this is the expected behaviour. You don't pass the id or timestamps as parameters when you submit a form creating a new photo, and neither should you in your functional tests. Using .attributes was just a convenient, but wrong, way of passing the attributes.
Let's figure out a better way.
I added the following to test_helper.rb:
tests/test_helper.rb def unprotected_attributes(obj) attributes = {} obj._accessible_attributes[:default].each do |attribute| attributes[attribute] = obj.send(attribute) unless attribute.blank? end attributes end
This returns a hash of the attributes which aren't protected, along with their values. For example, it might return something like this:
ruby-1.9.2-p290 :084 > unprotected_attributes(Photo.first) Photo Load (0.3ms) SELECT `photos`.* FROM `photos` LIMIT 1 => {"photo"=>http://photo_url.com/photo.jpg, "description"=>"My trip to France"}
And now in my photos_controller_test.rb I update
xhr :post, :create, photo: @photo.attributes, format: "json"
to
xhr :post, :create, photo: unprotected_attributes(@photo), format: "json"
Feel free to comment below with a more elegant solution!
UPDATE
@nzkoz pointed out that I shouldn't really post every attribute willy nilly, as that's not what a form does.
Using the above solution, if an attribute is unprotected, but shouldn't be, I won't find out about it because the test will still pass.If an attribute is protected, but it shouldn't be, again, the test will pass.
What I should do is this:
If I want to test that passing a photo and a description created a valid Photo, the test should be
xhr :post, :create, photo: @photo.attributes.slice("photo", "description"), format: "json"
By explicitly stating what I actually want, I can be sure that the tests are doing what I expect.
If an attribute is protected but shouldn't be, I'll get the exception and know to fix it.
I should also write a test which explicitly checks that the exception is raised for attributes I know I want to be protected.












