Reputation: 1866
I have a spec, with an object and two contexts. In one context I set one key to nil and in the other not:
describe SomeClass::SomeService, type: :model do
describe '#some_method' do
subject { described_class.new(params, current_user).some_method }
mocked_params = {
min_price: 0,
max_price: 100
}
let(:params) { mocked_params }
let(:current_user) { User.create(email: '[email protected]') }
context 'with invalid params' do
it 'returns nil if any param is nil' do
params[:min_price] = nil
expect(subject).to eq(nil)
end
end
context 'with valid params' do
it 'returns filtered objects' do
expect(subject).to eq([])
end
end
end
end
The problem is that the second test fails because min_price
is still nil.
database_cleaner
. Do I need it or not?let
method creates a new object every time it sees the variable. Since I have two contexts, and the subject
method is called in both of them, and inside the subject
I have the variable params
, why is the params
object not a new one with all the fields at every context?Upvotes: 0
Views: 1076
Reputation: 101811
I read that from rails 5 on I don't need database_cleaner. Do I need or not?
No. It's no longer needed. In previous versions of Rails the database transaction method of rolling back changes only worked (at times) with TestUnit/Minitest and fixtures.
I thought that the let method creates a new object every time it sees the variable. Since I have two context, and the subject method is called in both of them, and inside the subject I have the variable params, why the params object is not a new one at every context? (with all the fields)
This is completely wrong.
Use let to define a memoized helper method. The value will be cached across multiple calls in the same example but not across examples.
When you do:
mocked_params = {
min_price: 0,
max_price: 100
}
let(:params) { mocked_params }
You're really just returning a reference to the object mocked_params
and then mutating that object.
If you do:
let(:params) do
{
min_price: 0,
max_price: 100
}
end
You will get a new hash object on the first call to let
and the value will then be cached but not shared between examples. But that's really the tip of the iceberg with this spec.
describe SomeClass::SomeService, type: :model do
describe '#some_method' do
let(:current_user) { User.create(email: '[email protected]') }
# explicit use of subject is a serious code smell!
let(:service) { described_class.new(params, current_user) }
context 'with invalid params' do
# since let is lazy loading we can define it in this context instead
let(:params) do
{
min_price: nil,
max_price: 100
}
end
it 'returns nil if any param is nil' do
# actually call the method under test instead of misusing subject
# this makes it much clearer to readers what you are actually testing
expect(service.some_method).to eq(nil)
end
end
context 'with valid params' do
let(:params) do
{
min_price: 0,
max_price: 100
}
end
it 'returns filtered objects' do
expect(service.some_method).to eq([])
end
end
end
end
Its also pretty questionable why the object under test takes a hash as the first positional parameter and not as the last parameter which is the Ruby way.
Upvotes: 3
Reputation: 106792
This happens because you initialize the mocked_params
only once when the file is loaded and then you change that hash in the first test.
Instead create the params within the let
block which would lead to a re-creation of the hash for each test.
Change
mocked_params = {
min_price: 0,
max_price: 100
}
let(:params) { mocked_params }
to
let(:params) do
{
min_price: 0,
max_price: 100
}
end
Upvotes: 2