Rspec-rails: expect(subject).to assigns(instance_variable)

Created on 21 Apr 2014  路  9Comments  路  Source: rspec/rspec-rails

I faced with a difficult to test in rspec.

I love the subject & expect(subject).to way to declare tests in rspec, but it have some functionality missing (at least, few Stackoverflow discussions didn't help me).

Is there any way to tests controller assigns instance variables like that?

    subject { get :index }

    describe 'by owner of records' do
      it { expect(subject).to render_template(:index) } # works
      it { expect(subject).to assigns(:records).to([some_array]) # it should test controllers assigns @records.

Most helpful comment

Rails 5 Update:

assigns is deprecated

Though it can be a code smell, to upgrade older apps you can transition to #instance_variable_get.

Rspec example:
expect(@controller.instance_variable_get(:@people).first).to eq(person)

All 9 comments

You need to separate your second assertion from the subject call, it's not designed to be used like that.

it "assigns records" do
  subject
  expect(assigns(:records).to eq [some_array]
end

You also should name your subject, or use a plain let, e.g. let(:list_records) { get :index } and reference that. An explicit subject call is considered a test smell.

@JonRowe but why it is code smell? I think, oneliner seems better than 2 locs. No?

Explicit use of subject is considered a test smell because you're not actually describing the system under test. Similarly one liners don't tend to produce specs that adequately describe what they're testing.

I think

expect(execute_action).to assigns(:instance_variable).to(value)

Is self-explanatory and isn't code smell.

@asiniy if you like that type of style (I tend to be a fan as well), check out the block from of expect and the change matcher:

expect{ execute_action }.to change{ assigns(:ivar) }.to(value)

Additionally, if you enjoy subject, please consider using a named subject; as it provides better description of what you are testing. Though, IMO subject should not be used for actions, only object instances.

subject(:purchased_foo) { Foo.new(purchased: true, cost: 100 }

@cupakromer thanks for detailed description.

@cupakromer & @JonRowe, what about this style in controller:

context 'controller "ACTION"' do
  let(:action) { get :action }
  it {  expect(action).to render_template(:action) }
  it_has_behavior 'restrict_guest' # shared examples
end

I use huge amounts of shared examples and don't want to pass anything to it.

Our later comments about naming were advice on style, or original point (that get :action doesn't and won't return anything you can use to assert assigns on) still stands. You must write such a spec over two lines, or use the change matcher. Either way you don't have to pass anything into a shared example.

Rails 5 Update:

assigns is deprecated

Though it can be a code smell, to upgrade older apps you can transition to #instance_variable_get.

Rspec example:
expect(@controller.instance_variable_get(:@people).first).to eq(person)

Was this page helpful?
0 / 5 - 0 ratings