Let say I want to test my controller behavior, but it accepts JSON string via GET.
Right now I have var in my test class #testJson, but from time to time some unexpected stuff happens to those JSONS (bad char inside i.e.). So I want to add another test case.
But adding another var #problematicJson1 (and there could be more probably) doesn't seems like a good idea.
What's is the best way to keep "fixtures" like that? Should I keep'em in files and load them? Is there some fixture feature i don't know about that could help?
Those things are not fixtures.
You should use a neat feature of RSpec (if you are using RSpec at all) that allows to lazily define variables, so the actual variable is instantiated only when used by a specific "it", even if it is defined in an outer "context/describe" block.
https://www.relishapp.com/rspec/rspec-core/v/2-6/docs/helper-methods/let-and-let
context "some context" do
let(:testJson) { put your json inside the block }
let(:otherJson) { {:my_json => textJson} } # this will use the defined testJson
it "something" do
testJson.should have_key "blah"
end
context "some internal context"
let(:testJson) { something else }
it "some other test" do
otherJson[:my_json].should ....
# this will use the local version of testJson
# you only have to redefine the things you need to, unlike a before block
end
end
end
Related
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.
In the process of writing tests in Rspec, if you encounter repeated required parameters {...}, you can use let to write it. This avoids writing a bunch of parameter preparations in advance for each example.
However, I don't quite understand the paradigm of Better Specs. His original code is this:
describe '#type_id' do
before { #resource = FactoryBot.create :device }
before { #type = Type.find #resource.type_id }
it 'sets the type_id field' do
expect(#resource.type_id).to eq(#type.id)
end
end
After using let it becomes the following
describe '#type_id' do
let(:resource) { FactoryBot.create :device }
let(:type) { Type.find resource.type_id }
it 'sets the type_id field' do
expect(resource.type_id).to eq(type.id)
end
end
It looks like the way to call a resource is pretty much the same, what's the benefit of using let ? What is the function of FactoryBot.create:device? And I can't see where type is being called?
The difference is that lets are lazily evaluated and then memoized for the rest of the spec.
So in the first example, first the before blocks run and set the values of #resource and #type, and then the spec runs.
In the second example, the spec runs and when it references 'resource' the let block runs and returns a value, then when 'type' is referenced is runs the let block for that. The let block for type itself references 'resource' so it gets the value for resource that was memoized from the first time resource was referenced.
For what its worth, I disagree that lets are 'better'. My team and I have found that all they do is make specs much harder to understand for very little benefit, and we have removed all use of them in all our projects.
In fact, I consider that most of 'better specs' is actually poor advice, so if you are struggling to understand why something is 'better', you are very much not alone :)
I'm using let(:foo) { create_foo() } inside my tests. create_foo is a test helper that does some fairly time expensive setup.
So everytime a test is run, foo is created, and that takes some time. However, the foo object itself does not change, I just want to unit test methods on that object, one by one, seperated into single tests.
So, is there a RSpec equivalent of let to share the variable across multiple examples, but keep the nice things like lazy loading (if foo isn't needed) and also the automatic method definition of foo so that it can be used in shared examples, without referencing it with a #foo?
Or do I have to simply define a
def foo
create_foo()
end
Can you just put it in shared examples but use memoization?
def foo
#foo ||= create_foo()
end
Using let in this way goes against what it was designed for. You should consider using before_all which runs once per test group:
before :all do
#instancevar = create_object()
end
Keep in mind that this may not be wise if create_object() hits a database since it may introduce coupling and maintain state between your tests.
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
api = double "myApi"
api.should_receive(:get_info).and_return({
# a 360 lines hash!
})
I want to provide the response that should return from this double.
But this response is a VERY long hash, and I don't want to clutter my spec file.
Instead I want to write the hash in separate file, and use it in my spec
So, What is the best practices around ?
The best practise would be to not stub out the whole hash. Surely your tests wont require each and every line. You would be better off stubbing out the few lines that each test will need in each test / context.
If you must stub the whole api, you can create a separate module in spec/support:
# spec/support/api_stub.rb
module ApiStub
def self.response
{
# hash
}
end
end
Since anything in that folder gets included automatically, you can then use ApiStub.response in your stub definition.