21

I've been really loving using contexts, subjects and its with rspec to really clean up my test code. Typical example:

context "as a user" do
  subject{ Factory :user }

  its(:name){ should == "Bob" }
end

What I can't figure out though is how I could make this condition dynamic (ie. based on other objects). its appears to instance eval the attribute within the block so I lose access to everything around it. I'd love to do something like:

its(:name){ should == subject.contact.name }

But I can't see any way of achieving this. Does anyone know if there is some a method proxied through to this instance eval that gives access to the origin object? Or if there's any other way I can use methods outside the scope of the instance of the attribute that I'm checking?

additional info

It seems as if subject within the its block gets changed to the actual attribute (name in this case)

Interestingly, I have access to any of my let methods, but again, I don't have access to my original subject in question.

Community
  • 1
  • 1
brad
  • 31,987
  • 28
  • 102
  • 155
  • Could you rephrase your question? It sounds like you're onto something interesting, but your example is bad, because you're trying to test inner implementation instead of outer behaviour. – sheldonh Nov 06 '11 at 15:35
  • my question has nothing to do with this particular implementation. Rather, I want to reference my `user` set as subject within my `its` block, but it seems after some exploration that subject within `its` now becomes the attribute (`name` in this case) – brad Nov 07 '11 at 17:15
  • 1
    Note that this is probably not a great idea, because it won't give you a meaningful description in your spec report: you'll see `should == "Bob"` in the report, whereas what you really want to see is `should equal the contact's name` -- and there's no way to do that with `its` syntax that I'm aware of. I love `its`, but I only use it to match constants because of this limitation. – Marnen Laibow-Koser Jul 19 '12 at 15:53

3 Answers3

29

You are almost answering yourself, use a let assignment before you set the subject. Then you can reference it everywhere:

context "as a user" do
  let(:user) { Factory(:user) }
  subject { user }
  its(:name) { should == user.contact.name }
end

I'm a context, subject, its lover too !

David
  • 4,080
  • 1
  • 26
  • 34
1

Not sure if there's a way to do exactly what you're asking for since the "subject" within the block becomes the return value of User#name.

Instead, I've used the let method along with the following spec style to write this kind of test:

describe User do

  describe '#name' do
    let(:contact) { Factory(:contact, name: 'Bob') }
    let(:user) { Factory(:user, contact: contact) }

    subject { user.name }

    it { should == 'Bob' }
  end

end

This of course makes some assumptions about what your contact represents (here it's an association or similar). You may also choose to stub the return value of User#contact instead of relying on FactoryGirl to set up a "real" association.

Regardless of the choices you make on those fronts, this strategy has worked well for me. I find it allows me to be more concise about what is under test, while preserving info (for other devs & future self) about where the expected return value is coming from.

Anthony Navarre
  • 297
  • 2
  • 7
  • thanks, I think moreso then to answer my question, something like: `it{ should == contact.name }` should work since I appear to have access to `contact` in this scope – brad Nov 07 '11 at 17:20
  • 1
    Yes, that should work, but in my opinion you should be explicit about the return value ... eg: verify that it really is `'Bob'` and don't rely on `Contact#name` returning the correct value in your assertion. Not a big deal in this case, but consider the possibility that both of those methods inadvertently return `nil` - the test would pass even though the return values are incorrect. – Anthony Navarre Dec 10 '11 at 23:58
-5

You can just set an instance variable within the subject block:

context 'as a user' do
  subject { @user = FactoryGirl.create(:user) }

  its(:name) { should == @user.name }
end