Which Rspec convention is more up to date? - ruby-on-rails

Which Rspec convention is more up to date and should be used in new projects ?
subject { [] }
it { should == [] }
or
subject { [] }
it { expect(subject).to eq([]) }
I haven't found way of composing shorter version using subject implicitly with expect method.

Using expect (your second example) is the more up to date version. Rspec is moving away from methods being added onto existing objects (such as should) because they can occasionally cause odd behavior.

As stated in http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
"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"
I copy from this site: "The underlying problem is RSpec’s should syntax: for should to work properly, it must be defined on every object in the system… but RSpec does not own every object in the system and cannot ensure that it always works consistently. As we’ve seen, it doesn’t work as RSpec expects on proxy objects. Note that this isn’t just a problem with RSpec; it’s a problem with minitest/spec’s must_xxx syntax as well."
Personally, I use a mix of both still, as all "shoulda" matchers use the old syntax anyway, and are so easy to use...

Related

Class has leaked into another example and can no longer be used in spec

I'm unable to replicate this locally, but for some reason I am getting the following error when running tests in CircleCi:
<Double Mylogger> was originally created in one example but has leaked into another example and can no longer be used. rspec-mocks' doubles are designed to only last for one example, and you need to create a new one in each example you wish to use it for.
Here is a simplified version of my code:
# frozen_string_literal: true
describe 'my_rake_task' do
let(:my_log) { Mylogger.new }
subject { Rake::Task['my_rake_task'].execute }
describe 'one' do
context 'logs' do
let(:logs) do
[
['My message one'],
['My message two'],
]
end
after { subject }
it 'correctly' do
logs.each { |log| expect(my_log).to receive(:info).with(*log) }
end
end
end
describe 'two' do
context 'logs' do
let(:logs) do
[
['My message three'],
['My message four'],
]
end
after { subject }
it 'correctly' do
logs.each { |log| expect(my_log).to receive(:info).with(*log) }
end
end
end
end
Why is it saying MyLogger is a double? Why would it be leaking?
The reason that the error is saying that MyLogger is a double is because it is one. When you call expect(my_log).to receive or allow(my_log).to receive, you transform the instance into a partial-double.
As for why my_log is leaking: it's impossible to tell from the code that you posted. In order to cause a leak, some code either in your rake task or in the spec itself would need to be injecting my_log into some global state, like a class variable.
Mostly commonly this sort of thing is caused by storing something in a class variable. You will have to figure out where that is, and how to clear it or avoid using a class variable - it could be in your class or in a gem.
Best practice, where using a class variable or an external system is causing inter-test issues, is to clean this sort of thing between tests, if possible. For example ActionMailer::Base.deliveries and Rails.cache are common things that should be cleared. You should also clear Faker::UniqueGenerator or RequestStore if you're using those gems, and I'm sure there are more.
Once you have found the class variable, if it's in your code, and you have determined a class variable is the correct approach, you can add a reset or clear class method to the class and call it in a before(:each) RSpec block in your spec_helper.rb or rails_helper.rb.
Note that while a lot of things will clear themselves automatically between tests (such as RSpec mocks), and make you think this is all automatic, in practice it is often anything but.
Tests will only remain independent by (a) mostly making use of objects created in the tests and mostly only storing data in there and (b) ensuring anything else is cleared between tests by your explicit code or within the responsible gem.
This can be especially annoying when dealing with external third-party systems, which rarely provide an API to clear a staging environment, and hence sometimes require considerable care even when using the vcr gem.

Feature spec pass locally but fail on Circle CI

I have feature specs for my ActiveAdmin view. It works on my local machine. But when ran in CircleCi it fails with
undefined method `text' for nil:NilClass
spec
it 'uses the update_at date when prepaid_invoice' do
travel_to(5.days.ago) { create_prepayment }
travel_to(3.days.ago) do
visit '/admin/payments'
expect(page).not_to have_css('.col.col-created > div')
amount = all('.col-amount').last
expect(amount.text).to eq('$1,000.00') # failing here
all behaves different depending on whether you're on a current release of Capybara or the older 2.x version. In current versions all will wait for up to Capybara.default_max_wait_time seconds for any matching elements to appear and if none do it will return an empty array (actually Array like Result object, but close enough). In 2.x all (by default) did no waiting for matching elements, it would just return the empty array if no elements matched. Either way - you're not finding any matching elements, and then calling last on an empty array - giving you nil.
There are a couple of ways around this. You could tell all that you expect at least one matching element ( which will then force waiting for matching elements Capybara 2.x )
amount = all('.col-amount', minimum: 1).last
or depending on exactly what you're checking you could just combine it into one
expect(page).to have_css('.col-amount', exact_text:'$1,000.00')
which gets away from calling methods on Capybara elements and using the generic RSpec matchers on them (which is something you don't want to do, for test stability reasons, unless you have no other options). Those two solution don't test exactly the same thing, but may test enough for what you want. Depending on exactly how your HTML is structured there may be more efficient solutions too.
If you're already using a recent version of Capybara then your error would indicate that what you expect to be on the page isn't (maybe you're on an error page, etc) or you don't have Capybara.default_max_wait_time set high enough for the system you're testing on.

How does Rspec 'let' helper work with ActiveRecord?

It said here https://www.relishapp.com/rspec/rspec-core/v/3-5/docs/helper-methods/let-and-let what variable defined by let is changing across examples.
I've made the same simple test as in the docs but with the AR model:
RSpec.describe Contact, type: :model do
let(:contact) { FactoryGirl.create(:contact) }
it "cached in the same example" do
a = contact
b = contact
expect(a).to eq(b)
expect(Contact.count).to eq(1)
end
it "not cached across examples" do
a = contact
expect(Contact.count).to eq(2)
end
end
First example passed, but second failed (expected 2, got 1). So contacts table is empty again before second example, inspite of docs.
I was using let and was sure it have the same value in each it block, and my test prove it. So suppose I misunderstand docs. Please explain.
P.S. I use DatabaseCleaner
P.P.S I turn it off. Nothing changed.
EDIT
I turned off DatabaseCleaner and transational fixtures and test pass.
As I can understand (new to programming), let is evaluated once for each it block. If I have three examples each calling on contact variable, my test db will grow to three records at the end (I've tested and so it does).
And for right test behevior I should use DatabaseCleaner.
P.S. I use DatabaseCleaner
That's why your database is empty in the second example. Has nothing to do with let.
The behaviour you have shown is the correct behaviour. No example should be dependant on another example in setting up the correct environment! If you did rely on caching then you are just asking for trouble later down the line.
The example in that document is just trying to prove a point about caching using global variables - it's a completely different scenario to unit testing a Rails application - it is not good practice to be reliant on previous examples to having set something up.
Lets, for example, assume you then write 10 other tests that follow on from this, all of which rely on the fact that the previous examples have created objects. Then at some point in the future you delete one of those examples ... BOOM! every test after that will suddenly fail.
Each test should be able to be tested in isolation from any other test!

Rspec mocks, can 'expect' also stub a method as a side effect?

I'm trying to make sense of the tests in an inherited app, and I need some help.
There are lots of spec groups like this one (view spec):
let(:job_post) { FactoryGirl.create(:job_post) }
# ...
before do
expect(view).to receive(:job_post).at_least(:once).and_return(job_post)
end
it "should render without error" do
render
end
... with job_post being an helper method defined on the controller. (yes, they could have used #instance variables, and I'm in the process of refactoring it).
Now, in my opinion using an expect inside a before block is wrong. Let's forget about that for a second.
Normally the test above is green.
However, if I remove the expect line, the test fails. It appears that in this case expect is stubbing the method on the view. In fact, replacing expect with allow seems to have exactly the same effect.
I think that what's going on is that normally – when run with a server – the view will call job_posts and the message will land on the helper method on the controller, which is the expected behaviour.
Here, however, expect is setting an expectation and, at the same time, stubbing a method on the view with a fixed return value. Since the view template will call that method, the test passes.
About that unexpected "stub" side effect of expect, I've found this in the rspec-mocks readme:
(...) We can also set a message expectation so that the example fails if find is not called:
person = double("person")
expect(Person).to receive(:find) { person }
RSpec replaces the method we're stubbing or mocking with its own test-double-like method. At the end of the example, RSpec verifies any message expectations, and then restores the original methods.
Does anyone have any experience with this specific use of the method?
Well, that's what expect().to receive() does! This is the (not so) new expectation syntax of rspec, which replaces the should_receive API
expect(view).to receive(:job_post).at_least(:once).and_return(job_post)
is equivalent to
view.should_receive(:job_post).at_least(:once).and_return(job_post)
and this API sets the expectation and the return value. This is the default behavior. To actually call the original method as well, you need to explicitly say so:
view.should_receive(:job_post).at_least(:once).and_call_original
On to some other issues:
(yes, they could have used #instance variables, and I'm in the process of refactoring it).
let API is very ubiquitous in rspec testing, and may be better than #instance variables in many cases (for example - it is lazy, so it runs only if needed, and it is memoized, so it runs at most once).
In fact, replacing expect with allow seems to have exactly the same effect.
The allow syntax replaces the stub method in the old rspec syntax, so yes, it has the same effect, but the difference is, that it won't fail the test if the stubbed method is not called.
As the OP requested - some explanations about should_receive - unit tests are expected to run in isolation. This means that everything which is not directly part of your test, should not be tested. This means that HTTP calls, IO reads, external services, other modules, etc. are not part of the test, and for the purpose of the test, you should assume that they work correctly.
What you should include in your tests is that those HTTP calls, IO reads, and external services are called correctly. To do that, you set message expectations - you expect the tested method to call a certain method (whose actual functionality is out of the scope of the test). So you expect the service to receive a method call, with the correct arguments, one or more times (you can explicitly expect how many times it should be called), and, in exchange for it actually being called, you stub it, and according to the test, set its return value.
Sources:
Message expectation
RSpec's New Expectation Syntax
Rspec is a meta-gem, which depends on the rspec-core, rspec-expectations and rspec-mocks gems.
Rspec-mocks is a test-double framework for rspec with support for method stubs, fakes, and message expectations on generated test-doubles and real objects alike.
allow().to receive
is the use of 'Method Stubs', however
expect().to receive
is the use of 'Message Expectations'
You can refer to the Doc for more details
If you don't want to stub as a side affect, you can always call the original.
https://relishapp.com/rspec/rspec-mocks/v/2-14/docs/message-expectations/calling-the-original-method
For example I once wanted to spy on a method, but also call the function else it has other side affects. That really helped.
https://relishapp.com/rspec/rspec-mocks/v/2-14/docs/message-expectations/calling-the-original-method

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.

Resources