27

I wrote a simple class method Buy.get_days(string), and is trying to test it with different text string inputs. However I feel it is very verbose.

  • Is there any more concise way to test the following?
  • Is there a equivalent of subject for methods which I can just keep passing different parameters in and check the results?
  • Is there a way to avoid the unnecessary description at each it?

thanks

 describe Buy do
   describe '.get_days' do
    it 'should get days' do
      Buy.get_days('Includes a 1-weeknight stay for up to 4 people')
      .should == 1
      end
    it 'should get days' do
      Buy.get_days('Includes a 1-night stay in a King Studio Room with stone fireplace')
      .should == 1
    end
    it 'should get days' do
      Buy.get_days('Includes 4 nights/5 days at the Finisterra Hotel for up to two adults and two children (staying in the same room)')
      .should == 4
    end
  end
end
lulalala
  • 17,572
  • 15
  • 110
  • 169
  • 4
    How is the `it` description unnecessary? Just because you wrote the same text for specs that test different things doesn't mean the description shouldn't be there--maybe re-word them so they're useful? – Dave Newton Nov 04 '11 at 05:13
  • the input/output combination is descriptive enough (for me at least). – lulalala Nov 04 '11 at 05:21
  • can you give an example of rewording to make it more useful, @DaveNewton ? – ahnbizcad Mar 18 '15 at 18:07
  • @ahnbizcad Hopefully three different blocks, each of which `'should get days'` (e.g., they say the *same* thing), is an obvious smell. *Why* are the expectations different? There's something about them that makes the result be `1`, `1`, and `4`. The purpose of the string arg to `it` is to describe in human-readable terms what the block is actually testing. I can't reword the OP's descriptions because I don't know why the blocks should return the blocks that they do. – Dave Newton Mar 18 '15 at 19:25

5 Answers5

24

Apparently there is a described_class method.

https://www.relishapp.com/rspec/rspec-core/docs/metadata/described-class

I suppose it's cleaner than subject.class, since it doesn't introduce another . method call, which reduces readability.

Using either described_class or subject.class may be more DRY than mentioning the class explicitly in every example. But personally I think not getting the syntax highlighting that comes with mentioning the class name explicitly is kind of a bummer, and I think it reduces readability, despite it totally winning in the maintainability department.

A question arises regarding best practice:

Should you use described_class whenever possible inside and outside the .expect() method, or only within the expect() method?

ahnbizcad
  • 10,491
  • 9
  • 59
  • 85
  • This is what I prefer to use in my tests, leaving subject for the data tested through said methods. – DBrown Oct 13 '17 at 18:21
14

There isn't a subject equivalent for calling a method, so using it is the way to go here. The issue I see with your code as presented is that it doesn't actually explain what you are testing for. I would write something more like:

describe Buy do
  describe '.get_days' do
    it 'should detect hyphenated weeknights' do
      Buy.get_days('Includes a 1-weeknight stay for up to 4 people').should == 1
    end
    it 'should detect hyphenated nights' do
      Buy.get_days('Includes a 1-night stay in a King Studio Room with stone fireplace').should == 1
    end
    it 'should detect first number' do
      Buy.get_days('Includes 4 nights/5 days at the Finisterra Hotel for up to two adults and two children (staying in the same room)').should == 4
    end
  end
end

I'm making assumptions about what you're after here, but hopefully the idea is clear. This will also lead to much more helpful error output when a test fails. Hope this helps!

Matt Sanders
  • 8,023
  • 3
  • 37
  • 49
8

This might be an old question but you can always use subject.class to get by:

describe Buy do
  describe '.get_days' do
    it { expect(subject.class.get_days('Includes a 1-weeknight stay for up to 4 people')).to eq 1 }
  end
end
Omar Ali
  • 8,467
  • 4
  • 33
  • 58
  • Good catch! facepalm. I should have known this. This is the correct answer! – ahnbizcad Mar 18 '15 at 17:10
  • 1
    `subject.class`? Wouldn't it be clearer to use `Buy`? – B Seven Mar 18 '15 at 17:25
  • 1
    @ahnbizcad Not really, because this shows only a single test. Think about the spec output and what it's supposed to be showing. Adding the other tests with no descriptive tests makes for essentially useless spec output because you don't know (a) what the spec is testing, and (b) what differentiates the different specs. – Dave Newton Mar 18 '15 at 19:27
5

This is an interesting though perhaps more obtuse way to use the 'subject' block with Class methods.

Edit: The broken link as reported by the Wayback Archive which I suppose is susceptible to the same problem.

TCopple
  • 880
  • 7
  • 14
0

An alternative to using subject/it is to use before/specify:

describe '#destroy' do
  context 'with children' do
    before { @parent = FactoryGirl.create(:parent, children: FactoryGirl.create_list(:child, 2) }
    specify { @parent.destroy.should be_false }
  end
end

This will produce a reasonable description in RSpec's -fd output format:

#destroy
  with children
    should be false
bjnord
  • 2,734
  • 2
  • 23
  • 24