Multiple should statements in one rspec it clause - bad idea? - ruby-on-rails

Here's my rspec test:
it "can release an idea" do
james.claim(si_title)
james.release(si_title)
james.ideas.size.should eq 0
si_title.status.should eq "available"
end
Are the two should lines at the end a really bad idea? I read somewhere that you should only test for one thing per it block, but it seems silly to make a whole test just to make sure that the title status changes (the same function does both things in my code).

My interpretation of this isn't so much that there should be precisely one assertion / call to should per spec, but that there should only be one bit of behaviour tested per spec, so for example
it 'should do foo and bar' do
subject.do_foo.should be_true
subject.do_bar.should be_true
end
is bad - you're speccing 2 different behaviours at the same time.
on the other hand if your 2 assertions are just verifying different aspects of one thing then I'm ok with that, for example
it 'should return a prime integer' do
result = subject.do_x
result.should be_a(Integer)
result.foo.should be_prime
end
For me it wouldn't make too much sense to have one spec that checks that it returns an integer and a separate one that it returns a prime.
Of course in this case, the be_prime matcher could easily do both of these checks - perhaps a good rule of thumb is that multiple assertions are ok if you could sensibly reduce them to 1 with a custom matcher (whether actually doing this is actually worthwhile probably depends on your situation)
In your particular case, it could be argued that there are 2 behaviours at play - one is changing the status and other is mutating the ideas collection. I would reword your specs to say what the release method should do -
it 'should change the status to available'
it 'should remove the idea from the claimants ideas'
At the moment those things always happen simultaneously but I would argue they are separate behaviours - you could easily imagine a system where multiple people can claim/release an idea and the status only change when the last person releases the idea.

I have the same problem...
It ought to be one should per it (my boss says) by it makes testing time consuming and, as you said, silly.
Tests require good sense and flexibility or they might end up enslaving you.
Anyway, I agree with your test.

My policy is to always consider multiple assertions to be a sign of a potential problem and worth a second thought, but not necessarily wrong. Probably 1/3 of the specs I write end up having multiple assertions in them for one reason or another.
One of the problems with having multiple assertions is that when one fails, you can't see if the other one passed or not. This can sometimes be worked around by building an array of results and asserting the value of the array.
In the case you are asking about, I don't think that multiple assertions are a problem, but I do see something else that seems an issue to me. It looks like your spec may be overly coupled.
I think that you're trying to assert the behavior of whatever kind of a thing james is, but the test is also depending on behavior of si_title in order to tell us what was done to it (by way of its eventual #status value). What I would usually do instead is make si_title a test-double and use #should_receive to directly specify the messages that it should expect.

I think Frederick Cheung gave a very good answer (+1) especially the why, but I'd also like to give a comparison bit of code for you to look at which uses the its, lets, before and context syntax:
context "Given a si_title" do
let(:james) { James.new } # or whatever
before do
james.claim(si_title)
james.release(si_title)
end
context "That is valid" do
let(:si_title) { Si_title.new } # or whatever
describe "James' ideas" do
subject { james.ideas }
its(:size) { should == 0 }
its(:whatever) { should == "Whatever" }
end
describe "Si title" do
subject { si_title }
its(:status) { should == "available" }
end
end
context "That is invalid" do
# stuff here
end
end
I would even go further and make the expected values lets and then make the examples shared_examples, so that they could then be use to check the different aspects (null arguments, invalid arguments, an incorrect object… ), but I believe that this is a far better way to spec your intentions and still cut down on any repetition. Taking the example from Frederick's answer:
it 'should return a prime integer' do
result = subject.do_x
result.should be_a(Integer)
result.foo.should be_prime
end
Using RSpec's syntax to full effect you get this:
let(:result) { 1 }
subject{ result }
its(:do_x) { should be_a(Integer) }
its(:foo) { should be_prime }
Which means you can check multiple aspects of a subject.

Related

Multiple assertions for single setup in RSpec

I have a few slower specs that I would like to optimise.
The example of such spec looks like:
require 'rspec'
class HeavyComputation
def compute_result
sleep 1 # something compute heavy here
"very big string"
end
end
describe HeavyComputation, 'preferred style, but slow' do
subject { described_class.new.compute_result }
it { should include 'big' }
it { should match 'string' }
it { should match /very/ }
# +50 others
end
This is very readable and I'm happy with it generally, except that every additional spec will add at least 1 second to the total run-time. That is not very acceptable.
(Please let's not discuss the optimisation on the HeavyComputation class as it is outside of the scope of this question).
So what I have to resort to is spec like this:
describe HeavyComputation, 'faster, but ugly' do
subject { described_class.new.compute_result }
it 'should have expected result overall' do
should include 'big'
should match 'string'
should match /very/
# +50 others
end
end
This is obviously much better performance wise because the time to run it will always be nearly constant.
The problem is that failures are very hard to track down and it is not very intuitive to read.
So ideally, I would like to have a mix of both. Something along these lines:
describe HeavyComputation, 'what I want ideally' do
with_shared_setup_or_subject_or_something_similar_with do
shared(:result) { described_class.new.compute_result }
subject { result }
it { should include 'big' }
it { should match 'string' }
it { should match /very/ }
# +50 others
end
end
But unfortunately I cannot see where to even start implementing it. There are multiple potential issues with it (should the hooks be called on shared result is among those).
What I want to know if there is an existing solution to this problem.
If no, what would be the best way to tackle it?
You can use a before(:context) hook to achieve this:
describe HeavyComputation, 'what I want ideally' do
before(:context) { #result = described_class.new.compute_result }
subject { #result }
it { should include 'big' }
it { should match 'string' }
it { should match /very/ }
# +50 others
end
Be aware that before(:context) comes with a number of caveats, however:
Warning: before(:context)
It is very tempting to use before(:context) to speed things up, but we
recommend that you avoid this as there are a number of gotchas, as well
as things that simply don't work.
context
before(:context) is run in an example that is generated to provide group
context for the block.
instance variables
Instance variables declared in before(:context) are shared across all the
examples in the group. This means that each example can change the
state of a shared object, resulting in an ordering dependency that can
make it difficult to reason about failures.
unsupported rspec constructs
RSpec has several constructs that reset state between each example
automatically. These are not intended for use from within before(:context):
let declarations
subject declarations
Any mocking, stubbing or test double declaration
other frameworks
Mock object frameworks and database transaction managers (like
ActiveRecord) are typically designed around the idea of setting up
before an example, running that one example, and then tearing down.
This means that mocks and stubs can (sometimes) be declared in
before(:context), but get torn down before the first real example is ever
run.
You can create database-backed model objects in a before(:context) in
rspec-rails, but it will not be wrapped in a transaction for you, so
you are on your own to clean up in an after(:context) block.
(from http://rubydoc.info/gems/rspec-core/RSpec/Core/Hooks:before)
As long as you understand that your before(:context) hook is outside the normal per-example lifecycle of things like test doubles and DB transactions, and manage the necessary setup and teardown yourself explicitly, you'll be fine -- but others who work on your code base may not be aware of these gotchas.
#Myron Marston gave some inspiration, so my first attempt to implement it in a more or less reusable way ended up with the following usage (note the shared_subject):
describe HeavyComputation do
shared_subject { described_class.new.compute_result }
it { should include 'big' }
it { should match 'string' }
it { should match /very/ }
# +50 others
end
The idea is to only render subject once, on the very first spec instead of in the shared blocks.
It makes it pretty much unnecessary to change anything (since all the hooks will be executed).
Of course shared_subject assumes the shared state with all its quirks.
But every new nested context will create a new shared subject and to some extent eliminates a possibility of a state leak.
More importantly, all we need to do in order to deal the state leaks s(should those sneak in) is to replace shared_subject back to subject. Then you're running normal RSpec examples.
I'm sure the implementation has some quirks but should be a pretty good start.
aggregate_failures, added in version 3.3, will do some of what you're asking about. It allows you to have multiple expectations inside of a spec, and RSpec will run each and report all failures instead of stopping at the first one.
The catch is that since you have to put it inside of a single spec, you don't get to name each expectation.
There's a block form:
it 'succeeds' do
aggregate_failures "testing response" do
expect(response.status).to eq(200)
expect(response.body).to eq('{"msg":"success"}')
end
end
And a metadata form, which applies to the whole spec:
it 'succeeds', :aggregate_failures do
expect(response.status).to eq(200)
expect(response.body).to eq('{"msg":"success"}')
end
See: https://www.relishapp.com/rspec/rspec-core/docs/expectation-framework-integration/aggregating-failures

How do I test `rand()` with RSpec?

I have a method that does something like this:
def some_method
chance = rand(4)
if chance == 1 do
# logic here
else
# another logic here
end
end
When I use RSpec to test this method, rand(4) inside it always generates 0. I am not testing rand method of Rails, I am testing my method.
What is a common practice to test my method?
There are two approaches I would consider:
Approach 1:
Use a known value of seed in srand( seed ) in a before :each block:
before :each do
srand(67809)
end
This works across Ruby versions, and gives you control in case you want to cover particular combinations. I use this approach a lot - thinking about it, that's because the code I was testing uses rand() primarily as a data source, and only secondarily (if at all) for branching. Also it gets called a lot, so exerting call-by-call control over returned values would be counter-productive, I would end up shovelling in lots of test data that "looked random", probably generating it in the first place by calling rand()!
You may wish to call your method multiple times in at least one test scenario to ensure you have reasonable coverage of combinations.
Approach 2:
If you have branch points due to values output from rand() and your assertions are of the type "if it chooses X, then Y should happen", then it is also reasonable in the same test suite to mock out rand( n ) with something that returns the values you want to make assertions about:
require 'mocha/setup'
Kernel.expects(:rand).with(4).returns(1)
# Now run your test of specific branch
In essence these are both "white box" test approaches, they both require you to know that your routine uses rand() internally.
A "black box" test is much harder - you would need to assert that behaviour is statistically OK, and you would also need to accept a very wide range of possibilities since valid random behaviour could cause phantom test failures.
I'd extract the random number generation:
def chance
rand(4)
end
def some_method
if chance == 1 do
# logic here
else
# another logic here
end
end
And stub it:
your_instance.stub(:chance) { 1 }
This doesn't tie your test to the implementation details of rand and if you ever decide to use another random number generator, your test doesn't break.
It seems that best idea is to use stub, instead of real rand. This way you would be able to test all values that you are interested in. As rand is defined in Kernel module you should stub it using:
Kernel.stub(:rand).with(anything) { randomized_value }
In particular contexts you can define randomized_value with let method.
I found that just stubbing rand ie. using Kernel.stub(:rand) as answered by Samuil did not initially work. My code to be tested called rand directly e.g
random_number = rand
However, if I changed the code to
random_number = Kernel.rand
then the stubbing worked.
This works in RSpec:
allow_any_instance_of(Object).to receive(:rand).and_return(1)

RSpec 'Expect' Syntax and Idiomatic attribute spec

This should have an easy answer but I'm struggling to find it (have checked RSpec documentation, EverydayRails Testing with RSpec, Google results). In my model specs I like to include basic attribute specs as follows:
describe Foo do
describe "basic attributes" do
before { #foo = create(:foo) }
subject { #foo }
it { should be_valid }
it { should respond_to(:color) }
it { should respond_to(:height) }
it { should respond_to(:some_other_attribute) }
it { should respond_to(:you_get_the_idea) }
...
I like these specs because if there is some kind of error within my factory and/or model these specs help me pinpoint it quickly.
I've incorporated the expect syntax into all other specs and I like how it reads, but how to use it here? One option might be
expect(#foo).to respond_to(:color)
And another might be
expect(it).to respond_to(:color)
The former involves duplication that is avoided with the should syntax, but the latter looks strange to me (which could just be me).
I realize this question is more about style than functionality*, but we Ruby developers are conscientious about style, and I want to adhere to standard practices and have readable, idiomatic code. Any help is appreciated. Thanks.
UPDATE: Neither of my proposed options actually work, by the way. They both throw undefined method 'expect' errors. Now I'm really confused!
Having thought about the error, I realize it's because the should specs above are within one-line block. The confusion, then, is how can I write a single-line block with the expect syntax? In light of this update, the question is very much about functionality and I'll be excited to hear others' thoughts.
4/2015 UPDATE
rspec > 3.0 has added yet another way of handling these, and it sounds like rspec ~> 4.0 will do away with the should syntax. Per Myron Masters:
Some users have expressed confusion about how this should relates to the expect syntax and if you can continue using it. It will continue to be available in RSpec 3 (again, regardless of your syntax configuration), but we've also added an alternate API that is a bit more consistent with the expect syntax:
describe Post do
it { is_expected.to allow_mass_assignment_of(:title) }
end
is_expected is defined very simply as expect(subject) and also supports negative expectations via is_expected.not_to matcher.
[...]
In RSpec 3, we've kept the should syntax, and it is available by default, but you will get a deprecation warning if you use it without explicitly enabling it. This will pave the way for it being disabled by default (or potentially extracted into a seperate gem) in RSpec 4, while minimizing confusion for newcomers coming to RSpec via an old tutorial.
Myron Marston, one of the core RSpec committers, explains here that you should still use
it { should be_cool }
If you've disabled the should syntax, he offers a solution to alias expect_it to it:
RSpec.configure do |c|
c.alias_example_to :expect_it
end
RSpec::Core::MemoizedHelpers.module_eval do
alias to should
alias to_not should_not
end
With this in place, you could write this as:
describe User do
expect_it { to be_valid }
end
I don't think there's a correct answer to this question, but I've been recently rewriting my test suites to move away from should and use the expect syntax exclusively, so I'll throw my two cents in. I decided to re-write pretty much because Myron Marston, the RSpec lead maintainer, wrote:
In the future, we plan to change the defaults so that only expect is available unless you explicitly enable should. We may do this as soon as RSpec 3.0, but we want to give users plenty of time to get acquianted with it.
He also commented:
We have no plans to ever remove "should"...but expect has fewer gotchas, and is the syntax I would recommend for new projects.
I do agree with Mark Rushakoff's answer as well, but personally I don't want to have to create those aliases just to keep a single it-block style syntax. So, using your example, where originally I wrote a model spec like your example in this form:
describe Foo do
let(:foo) { create(:foo) }
subject { foo }
describe "model attributes" do
it { should be_valid }
it { should respond_to(:color) }
it { should respond_to(:height) }
it { should respond_to(:some_other_attribute) }
it { should respond_to(:you_get_the_idea) }
end
# ...
end
I now would tend to write it like:
describe Foo do
let(:foo) { create(:foo) }
specify "model attributes" do
expect(foo).to be_valid
expect(foo).to respond_to(:color)
expect(foo).to respond_to(:height)
expect(foo).to respond_to(:some_other_attribute)
expect(foo).to respond_to(:you_get_the_idea)
end
# ...
end
My opinion is that referencing foo directly in expect(foo).to respond_to(:color) is as much "duplication" as referencing a subject indirectly using it, so I'm not too phased about it, and I'm warming to the way that expect specs generally read. But, ultimately I think this comes down to just being an issue of preferred writing style.

Keeping rspec convention with --format doc

Betterspecs suggests using something like:
subject { assigns('message') }
it { should match /it was born in Billville/ }
as good practice. But in case i want to run rspec in doc format (rspec -f doc) i'm receiving:
When you call a matcher in an example without a String, like this:
specify { object.should matcher }
or this:
it { should matcher }
RSpec expects the matcher to have a #description method. You should either
add a String to the example this matcher is being used in, or give it a
description method. Then you won't have to suffer this lengthy warning again.
So this
it "some desc" do
should match /it was born in Billville/
end
won't raise that annoying message but seems ugly.
Any ideas on how to keep rspec conventions and code clean, and still have some pretty output(like with -f doc)?
rspec v.2.13.0
As RSpec maintainer, there are many things listed on betterspecs.org with which I disagree. I've commented as such on the github issues for the project many months ago, but sadly, I don't think any of my concerns have been addressed :(.
Anyhow, I think the one-liner syntax is fine to use when the doc output matches what you want, but often it does not. Usually, the doc output of the one-liner syntax is overly specific, e.g. it returns in doc strings like should eq "dfgh" even though that is not a generally true behavior -- something like returns a string without vowels removed is a better, more generally true description of the behavior.
So my suggestion is to not use the one-liner syntax unless it produces the output you want. Don't use it just because betterspecs.org recommends it. Many of its recommendations are bad recommendations, in my opinion.
Personally, I agree with BetterSpecs on this. What is so difficult to understand about the following?
subject { assigns('message') }
it { should match /it was born in Billville/ }
If, as #Myron Marston opines, that it doesn't produce good enough output, then I'd suggest (and I always do) using contexts, e.g.
context "When given a message" do
subject { my_func arg }
context "With vowels" do
let(:arg) { "dafegih" }
let(:expected){ "dfgh" }
it { should == expected }
end
context "Without vowels" do #…
You'll get lovely output, and it also reads well as code, is terse, and encourages you to think about the different inputs, and reuse via shared examples and contexts that then encourages a test across a wider range of inputs. Using the string + block based way of writing specs encourages several specs to be crammed into one test, e.g.
it "some desc" do
should_not be_nil
should match /it was born in Billville/
end
If that fails was it because it was nil, or because it didn't match? Will that encourage reuse? This is far better, IMO:
subject { assigns('message') }
it{ should_not be_nil }
it { should match /it was born in Billville/ }
It's nice that the writers and maintainers of libraries intend for us to use libraries well, but I'm getting a bit tired of them thinking they can nag us or force us to do these things. RSpec has added itself to the list that includes Bundler and Rails headed "Projects that are really useful and I'm really grateful for, but should butt out of my business".

Is refactoring of tests/spec a good idea?

When I see in my test code specs like this for every controller:
it "#new displays input controls for topic title and keywords" do
ensure_im_signed_in
get :new
assert_response :success
assert_select "input#topic_title"
assert_select "input#topic_keywords_input"
assert assigns :topic
end
I want to refactor it and replace with some one-liner like this:
its_new_action_displays_input_form :topic, %w(input#topic_title input#topic_keywords_input)
and implementation:
def its_new_Action_displays_input_form field, inputs
it "#new displays input controls for #{inputs.join ", "}" do
ensure_im_signed_in
get :new
assert_response :success
for css in inputs
assert_select css
end
assert assigns field
end
end
What are the advantages of either keeping verbose version or refactoring to terser version?
I see only problem with refactored version is that RSpec does not show backtrace for the failed test.
The biggest question we always ask is "who is testing the tests?" You should definately go with refactoring your unit tests where ever you are and at whatever stage you are as it helps reduce the complexity. Reducing the complexity within your unit tests will yield the same benefits as reducing the complexity within your code base. I can see very little reason not to do it and many reasons to do it.
My very first job out of college was updating a set of unit tests. They were about 7 years old, with many deprecated methods being used. It took me eight months (there were a lot of them!) and I still didn't finish the job. However, I did manage to reduce the file size to about 1/3rd the original size and greatly simplify the work by refactoring much of it, and that helped speed up the work.
So, I would definitely encourage refactoring!
You refactor the spec because of the same reasons that you refactor everything else: It will make your application – be it you production app or tests suit – runs faster.
Also, refactoring a piece of code makes you think about the intention that you had to writing such a thing. This increases your comprehension of the problem and will make you more capable of solving other problems in the same application.
Yet I would strongly disagree about your refactoring. You see, when you are watching a spec you want to see fast and clear what are you testing. If you move the asserts to other place, you are hitting your self, because you need to go and find where did you put that thing.
I suggest you that never move the asserts out of your spec. Even if they are repeated. That's fine. It remarks the intention, so you never forget what you want to test.
Instead, focus your refactoring on boiler code.
For example, you are testing a form and to get to that page you need to click many links. A first approach would be to put everything in the spec. A lot of click_link.
I refactoring of such code will be put the whole bunch of click_link to a before(:each). Use Context as well, to clarify.
Like this:
feature "Course" do
context "Loged in" do
before(:each) do
school = School.make!
switch_to_subdomain(school)
end
context "In the new course form" do
before(:each) do
click_link("Asignaturas")
click_link("Nueva asignatura")
end
scenario "New course" do
fill_in(:name, :with => "Matematicas")
click_button("Create Course")
page.has_content?("Asignatura creada").should == true
dbSchool = School.find(school.id)
dbSchool.courses.count.should == 1
end
scenario "New course" do
fill_in(:name, :with => "")
click_button("Create Course")
page.has_content?("Asignatura creada").should == false
dbSchool = School.find(school.id)
dbSchool.courses.count.should == 0
end
end
end
end
You see? The boiler code is out of the specific spec. But let enough on the spec so that 7 month after that you can understand what you are testing. Never take away the asserts.
So my answer: Refactor Spec is not only a good idea, but a necessity. But don't mix refactoring with hiding code.
You should indeed refactor your tests to remove duplication. However, there are likely ways to do it that that keep the failure backtrace.
Rather than pulling everything out to a single method, you might do well to have a shared
before(:each) section for the common setup, and write your own matchers and/or assertions to combine the checks.

Resources