2013-12-30

Testing strong parameters

Pivot Robbie Clutton describes strong parameters as follows:
Strong parameters are a way of white listing HTTP query parameters and moves the burden of whitelisting from the ActiveModel/ActiveRecord classes and into the controllers.
Source
My view is that this testing strategy is far too complex, and here I outline a simpler approach.

First, let's understand the API. It was a bit tricky for me at first, partly because the documentation is vague.

We can whitelist attributes via the permit method, and we can require them via the require method.

Example

params.fetch(:project).require(:name)
params.fetch(:project).permit(:description)

Regarding the handling of missing or unpermitted keys, here's what the docs say:
By default parameter keys that are not explicitly permitted will be logged in the development and test environment. In other environments these parameters will simply be filtered out and ignored.
Additionally, this behaviour can be changed by changing the config.action_controller.action_on_unpermitted_parameters property in your environment files. If set to :log the unpermitted attributes will be logged, if set to :raise an exception will be raised.
The unpermitted key behavior happens instantly. That is, if you require a parameter that's missing, you get an ActionController::ParameterMissing exception. If you permit some parameters, any unpermitted parameters will instantly be indicated via an ActionController::UnpermittedParameters exception or a log line, depending on your action_on_unpermitted_parameters setting.

Note that this means you don't want to have a permit before a require if you're using the :raise option (which I strongly suggest you do, because that's confident coding), because the permit call would then raise an exception.

But what's this default behavior where keys that are not permitted are "filtered out and ignored"? This just means that the call to permit returns a hash in which the unpermitted keys are removed. The original params object is unaltered.

In any case, this brings us back to..

Testing

First off, recognize that it's easy to test that all the right parameters are permitted. We simply write a controller test using an update hash including all of the permitted parameters.

Now, to assert that any other params aren't permitted, we simply make a context in which we have a single unpermitted parameter, and assert that the unpermitted attribute isn't updated.

But now what about require? That's considerably more work to test, since you'd have to make nested contexts for every single required parameter. Therefore I advise simply not to use require at all. I'm not even sure why it was added. Feel free to tell me if you can think of a good reason.

This strategy is just radically simpler than the approach Robbie Clutton took.

2 comments:

Robbie said...

Hi Clay,

The require method is useful in place of where you have fetch. It ensures that some the request has data in the form that is expected and will raise the correct exception as expected if missing.

The testing thing is different, and my post is an exploration is trying to achieve single responsibility and removing redundancy of valid and invalid attributes across actions.

To be honest, it's not something that I've used a great deal. As you said, it's not the simplest thing to do and can add some extra weight.

Please bare in mind this was written when Rails 4 was newly released and this was blogging about what I discovered with it.

Cheers

Robbie

Ed Essey said...

Interesting perspective on not using require at all. I've wasted much time trying to figure out how to best rspec invalid cases concerning absent required parameters.