Mohamad
Mohamad

Reputation: 35349

Testing nested resources with RSpec results in a strange failure

I wrote this to test my controller's create action which uses a nested resources. I have an Account model with a has_many :users association. Upon sign up, an Account with a single user gets created.

  describe "POST #create", focus: true do
    let(:account) { mock_model(Account).as_null_object }

    before do
      Account.stub(:new).and_return(account)
    end

    it "creates a new account object" do
      account_attributes         = FactoryGirl.attributes_for(:account)
      user_attributes            = FactoryGirl.attributes_for(:user)
      account_attributes[:users] = user_attributes

      Account.should_receive(:new).with(account_attributes).and_return(account)
      post :create, account: account_attributes
    end
  end

This is the failure output I'm getting; notice the difference between expected and got: it expected a symbol while it got a string.

1) AccountsController POST #create creates a new account object
     Failure/Error: Account.should_receive(:new).with(account_attributes).and_return(account)
       <Account(id: integer, title: string, subdomain: string, created_at: datetime, updated_at: datetime) (class)> received :new with unexpected arguments
         # notice that expected has symbols while the other users strings...
         expected: ({:title=>"ACME Corp", :subdomain=>"acme1", :users=>{ ... }})
              got: ({"title"=>"ACME Corp", "subdomain"=>"acme1", "users"=>{ ... }})
     # ./spec/controllers/accounts_controller_spec.rb:34:in `block (3 levels) in <top (required)>'

I can't help but notice that this code also smells a little. I don't know if I am going about this right. I'm new to RSpec, so bonus points if you can provide some feedback on my effort.

Upvotes: 0

Views: 468

Answers (1)

Renato Zannon
Renato Zannon

Reputation: 29941

The params hash generally contains keys that are strings instead of symbols. While we do access them using symbols, it is due to the fact that it is a Hash with indifferent access, which doesn't care whether it is accessed using strings or symbols.

For your test to pass, you can use the stringify_keys method on the account_attributes hash when setting the expectation. Then, when Rspec compares the hashes both will be string-keyed.


Now, about the review you asked: instantiating an account is really an expectation that you have upon your controller? Your tests will be less brittle if you place your assertions/expectations upon more concrete, externally-visible behaviors, instead of upon each method that your object should use.

Rails controllers are generally brittle to test, because there are many equivalent ways to manipulate ActiveRecord models... I generally try to make my controllers as dumb as possible, and them I don't unit test them, leaving their behavior to be covered by higher-level integration tests.

Upvotes: 3

Related Questions