lorem monkey
lorem monkey

Reputation: 3992

Test strong parameters with minitest in Rails 4

I am new to Rails and I want to test my set strong parameters of the Book model with a controller test. I am using Minitest and Rails 4.

Book model:

class Book < ActiveRecord::Base
  validates :title, presence: true, length: { in: 1..150 }
end

Book controller wit params:

def create
  @book = Book.new book_params

  if @book.save
    redirect_to action: "index", notice: 'Success.'
  else
    render :new
  end
end

private
  def book_params
    params.require(:book).permit(:title, :cover_image)
  end

My idea for a test - does fail, because it creates an entry:

assert_no_difference('Book.count') do
  post :create, book: {
    id: 123,
    title: "Lorem ipsum"
  }
end

How can I get the tests go green and is it correct to test the strong parameters with a controller test?

Upvotes: 2

Views: 1228

Answers (3)

BenFenner
BenFenner

Reputation: 1088

I do like to test Strong Parameters in the controller. I also like to test them more directly, so this is how I do it.

First, I have a test helper that is required in my test/test_helper.rb file:
test/test_helpers/controller_strong_params_helper.rb

# frozen_string_literal: true

module ControllerStrongParamsHelper
  def assert_requires_param(param, &block)
    @controller.params = ActionController::Parameters.new()
    assert_raises(ActionController::ParameterMissing) { yield }

    @controller.params = ActionController::Parameters.new(stub_parameter: {})
    assert_raises(ActionController::ParameterMissing) { yield }

    # It's not enough to have an empty required parameter, there needs to be something inside.
    @controller.params = ActionController::Parameters.new(param => {})
    assert_raises(ActionController::ParameterMissing) { yield }

    @controller.params = ActionController::Parameters.new(param => '')
    assert_raises(ActionController::ParameterMissing) { yield }

    @controller.params = ActionController::Parameters.new(param => {something_inside: 'something'})
    assert_nothing_raised { yield }
  end
end

This lets me easily test the strong params that are not optional.

Now assume I have these strong params in my ExampleController:

def example_params
  params.require(:example).permit(:id,
                                  :name,
                                  :description)
end
private :example_params

This is what my minitest tests would look like:
test/controllers/example_controller_test.rb

###############################################
test '#example_params should require an example parameter' do
  assert_requires_param(:example) { @controller.send(:example_params) }
end

###############################################
test '#example_params should permit some expected example parameters' do

  # Using hash rockets so the equality check works.
  expected_permitted_params = { 'id'          => nil,
                                'name'        => nil,
                                'description' => nil }

  # Specifically merge in any potential non-attribute parameters here if present/needed.
  all_params = { example: Example.new.attributes }

  @controller.params = ActionController::Parameters.new(all_params)
  actual_permitted_params = @controller.send(:example_params)

  assert_equal(expected_permitted_params, actual_permitted_params)
end

Upvotes: 0

silverdr
silverdr

Reputation: 2192

I am looking for an answer to almost the same question. When using Rails 5 I eventually came up with a solution (call it workaround if you like :-) for testing that the unwanted params don't actually get through. In my (simplified here) case I want to disallow some "security critical" params being passed through when creating a new user.

In the controller (permitting only email and password):

    private
        def user_params
            params.require(:user).permit(:email, :password)
        end

In the integration test:

test "not permitted signup data submitted" do
    new_user_email = "tester_" + (0...10).map { ('0'..'9').to_a[rand(26)] }.join + "@testing.net"
    get signup_path
    assert_difference 'User.count', 1 do
        post signup_path, params: { user: { email: new_user_email, password: "testpassword", role_id: 1 } }
    end
    user = User.last
    assert user.email == new_user_email
    assert user.role_id == nil
end

Here I submit an additional, "sensitive" parameter role_id with the value of 1 (admin). I expect the user to be created. Then I read that newly (last) created user and expect it to have role_id empty (nil). To make the test fail I add :role_id to user_params. Removing it, makes the test pass. Obviously if your attribute can't be nil (aka NULL in SQL), you can test for default value being stored instead of the submitted one.

Upvotes: 3

lorem monkey
lorem monkey

Reputation: 3992

Since Rails drops all unpermitted parameters not in permit, the new record will be created, hence the test will be red.

Although, one can raise an exception with the action_on_unpermitted_parameters method when non-whitlisted parameters are submitted.

Upvotes: 2

Related Questions