Zach Morek bio photo

Zach Morek

Email

Adventures in RSpec Deprecations

We were going through a bundle upgrade and updated RSpec along the way. Some of our legacy code has some very complex behavior that we’re testing using stubs. Turns out that the Law of Demeter is a lot easier to break than the Laws of Thermodynamics. Rather than get caught up trying to unwind the test, we were hoping to fix our deprecation warnings and move onto making features that our customers care about.

We were going through a bundle upgrade and updated RSpec along the way.
Some of our legacy code has some very complex behavior that we’re testing using stubs.
Turns out that the Law of Demeter is a lot easier to break than the Laws of Thermodynamics.
Rather than get caught up trying to unwind the test, we were hoping to fix our deprecation warnings and move onto making features that our customers care about.

Our test was using stub_chain and thus needed to be updated.

describe 'A test that uses stub chains' do
  it 'gets a deprecation warning' do
    complex_model = FactoryGirl.create(:complex_model)
    complex_model.stub_chain(:complex_parent_model, :to_xml).
      and_return(complex_model.to_xml)
    # This line needs to get updated ^

    complex_model.related_models << FactoryGirl.build(:related_model)
    complex_model.save!

    expect {
      complex_model.destroy_related_models_not_in_parent_xml!
    }.to change(complex.model.related_models, :count).by(-1)
  end
end

We looked at the docs for 3.0:
Message Chains - Working with legacy code - RSpec Mocks - RSpec - Relish

We saw the related docs in 2.99:
stub a chain of methods - Method stubs - RSpec Mocks - RSpec - Relish

We then decided to update the test to match the docs as closely as possible.

describe 'A test that now uses message chains' do
  it 'fails mysteriously' do
    complex_model = FactoryGirl.create(:complex_model)
    allow(complex_model).to receive_message_chain(:complex_parent_model, :to_xml)
      { complex_model.to_xml }
    # Yay! We should be good to go! ^

    complex_model.related_models << FactoryGirl.build(:related_model)
    complex_model.save!

    expect {
      complex_model.destroy_related_models_not_in_parent_xml!
    }.to change(complex.model.related_models, :count).by(-1)
  end
end

Aaaaaand then our tests broke.
We just couldn’t figure out what was wrong.

AHHHHHHHHHH!

And then it dawned on us.

OOOOOOOOOOH!

We realized that by changing from and_return to a block we were throwing off the xml representation of our complex_model.
We wanted a snapshot of complex_model.to_xml instead of a fresh version every time.
In a real situation, the complex_parent_model would not have this problem.
We were using a block that would evaluate new every time.

Sonofa

Once we changed it back to using the method, we were good to go again.

describe 'A test that now uses message chains and return' do
  it 'works just right' do
    complex_model = FactoryGirl.create(:complex_model)
    allow(complex_model).to receive_message_chain(:complex_parent_model, :to_xml).
      and_return(complex_model.to_xml)
    # Rock on! ^

    complex_model.related_models << FactoryGirl.build(:related_model)
    complex_model.save!

    expect {
      complex_model.destroy_related_models_not_in_parent_xml!
    }.to change(complex.model.related_models, :count).by(-1)
  end
end

Turns out there’s a lot of different options for returning: Configuring responses - RSpec Mocks - RSpec - Relish

Nice

:wq