How to Repair an Old Gem (Part 3) - Upgrade RSpec
Checkout Part 1, and Part 2 of the series first!
Now that we can bundle install, and have a passing test suite with a very old version of RSpec, we need to upgrade RSpec.
This is most easily done with a tool called transpec
.
Before we can setup transpec
we need to upgrade to a minimum version of Ruby. Some generic requirements are:
- RSpec must be at least 2.14 or later.
- RSpec must be less than version 3.
First we’ll try version 2.99, as it was intended as a transitional version of RSpec between 2 and 3.
Initially the gemspec’s dependencies are strangely encoded due to a legacy rubygems issue that is no longer relevant, not even on Ruby 1.8.7. We start with this:
if s.respond_to? :specification_version then
s.specification_version = 4
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
s.add_development_dependency(%q<rake>.freeze, ["~> 0.8"])
s.add_development_dependency(%q<test-unit>.freeze, ["~> 3.5"])
s.add_development_dependency(%q<rspec>.freeze, ["~> 2.99"])
else
s.add_dependency(%q<rake>.freeze, ["~> 0.8"])
s.add_dependency(%q<test-unit>.freeze, ["~> 3.5"])
s.add_dependency(%q<rspec>.freeze, ["~> 2.99"])
end
else
s.add_dependency(%q<rake>.freeze, ["~> 0.8"])
s.add_dependency(%q<test-unit>.freeze, ["~> 3.5"])
s.add_dependency(%q<rspec>.freeze, ["~> 2.99"])
end
We’ll replace the entire block with:
s.add_development_dependency(%q<rake>.freeze, ["~> 0.8"])
s.add_development_dependency(%q<test-unit>.freeze, ["~> 3.5"])
s.add_development_dependency(%q<rspec>.freeze, ["~> 2.99"])
We bundle install
.
We also modify the RSpec config to work with both types of expectation syntaxes (should and assert).
RSpec.configure do |config|
config.expect_with :rspec do |c|
c.syntax = :should
end
config.expect_with :stdlib # assert syntax
end
Then commit the changes, because transpec
requires a clean git repo.
But we run into problems with our version of bundler.
$ transpec
You must use Bundler 2 or greater with this lockfile.
This error is confusing, as what it means is that transpec is trying to run with Bundler v1, but the Gemfile in our project requires v2. Transpec will not work with bundler v2, so we have to make more adjustments. Thanks to kaiwren for figuring this out (please upvote the PR)!
$ bundle _1.17.3_ install
Once again, commit everything to get back to a clean slate in git.
Then transpec
should work, and it does!
$ transpec
Copying the project for dynamic analysis...
Running dynamic analysis with command "bundle exec rspec"...
OS provides access to to underlying config values
#config, supplys the CONFIG hash
. should supply 'host_os'
. should be a kind of Hash
. should supply 'host_cpu'
... etc
Summary:
8 conversions
from: it { should ... }
to: it { is_expected.to ... }
7 conversions
from: it { should_not ... }
to: it { is_expected.not_to ... }
6 conversions
from: obj.should
to: expect(obj).to
5 conversions
from: obj.stub!(:message)
to: allow(obj).to receive(:message)
4 conversions
from: obj.stub(:message)
to: allow(obj).to receive(:message)
3 conversions
from: == expected
to: eq(expected)
2 conversions
from: pending
to: skip
1 conversion
from: obj.should_receive(:message)
to: expect(obj).to receive(:message)
1 conversion
from: obj.should_receive(:message).and_return
to: obj.should_receive(:message)
1 conversion
from: obj.should_receive(:message).any_number_of_times
to: allow(obj).to receive(:message)
Now we re-run specs, to ensure the translation didn’t break anything.
Lulz, it broke so much. But we’re well on our way to having an upgraded test suite.
Let’s fix noisy deprecation warnings.
:stdlib is deprecated. Use :test_unit or :minitest instead. Called from /Users/pboling/src/hub/moss/spec/config/rspec/rspec_core.rb:16:in `block in <top (required)>'.
The fix:
config.expect_with :test_unit
Running tests again, and all deprecations are fixed! We still have lots of failures though, all taking the form of:
Failure/Error: it { is_expected.to be_linux }
only the `receive` matcher is supported with `expect(...).to`, but you have provided: #<RSpec::Matchers::BuiltIn::BePredicate:0x00007fc3f686bb38>
This is a matcher issue, and we’ll want our “fixed” matchers to work on current latest version of RSpec,
so let’s upgrade to latest next, and then fix the matchers after.
We’ll need RSpec 3, but which version exactly? We’ll usee bundle outdated
to find out,
but first we need to delete the Gemfile.lock and run bundle update
to get back to a modern bundler.
rm Gemfile.lock
bundle update
bundle outdated
...
Gem Current Latest Requested Groups
rake 0.9.6 13.0.6 ~> 0.8 development
rspec 2.99.0 3.12.0 ~> 2.99 development
rspec-core 2.99.2 3.12.0
rspec-expectations 2.99.2 3.12.0
rspec-mocks 2.99.4 3.12.1
Addressing each of these… the rake
gem’s version will be determined by which version of Ruby we want to support.
Since os
has never declared a Ruby version constraint, and given the age of the code, it should be assumed to be 1.8.7.
Rake Version Constraint | Gemspec Ruby Version Constraint |
---|---|
~> 10.5 👈 |
>= 1.8.7 👈 |
~> 11.3 |
>= 1.9.3 |
~> 12.3 |
>= 2.0.0 |
~> 13.0 |
>= 2.2.0 |
So we can update that, and bundle install
again.
s.add_development_dependency(%q<rake>.freeze, ["~> 10.5"])
Specs are still failing same as before, so moving on to upgrading the outdated rspec
gems:
s.add_development_dependency(%q<rspec>.freeze, ["~> 3.12"])
And bundle install
again, and the same 22 of 32 specs are failing as before, so let’s look closer at the failure.
It should be clear why we upgraded first. The problem is the same,
but the newer version of RSpec has dramatically improved the developer experience!
Compare what the error was before, on RSpec v2.99:
only the `receive` matcher is supported with `expect(...).to`, but you have provided: #<RSpec::Matchers::BuiltIn::BePredicate:0x00007fc3f686bb38>
Now, on RSpec v3.12:
undefined method `expect' for #<RSpec::ExampleGroups::ForLinuxUbuntuUbuntu1004LTS::OS:0x00007fceb084c570>
So the issue is simply that we need to change the RSpec expect syntax to the one transpec
used:
config.expect_with :rspec do |c|
c.syntax = :expect # 👈 Change from :should to :expect
end
config.expect_with :test_unit # Some tests still use this syntax, which is fine.
Check the specs, and ensure everything is good!
$ bundle exec rspec
...
36 examples, 0 failures
RSpec test suite upgraded!
Check out the pull request and give it a thumbs up or a heart if you are feeling generous!
Stay tuned for further posts in this series!
comments powered by Disqus