AlbertMunichMar
AlbertMunichMar

Reputation: 1866

What is an equivalent to database_cleaner in Rails 6?

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.

  1. I read that from Rails 5 on I don't need database_cleaner. Do I need it or not?
  2. I thought that the 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

Answers (2)

max
max

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

spickermann
spickermann

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

Related Questions