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.
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:
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)
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)