consider the following code:
class Child
belongs_to :some_parent, optional: true
def status
some_parent&.calculate_status
end
end
Is that trivial enough to not write a unit test for each scenario or is it better to write scenarios such as:
describe '#status' do
context 'when some_parent does not exist' do
## assume let/before block exist to properly set this up
it 'returns nil' do
expect(subject.status).to_be nil
end
end
context 'when some_parent exists' do
## assume let/before block exist to properly set this up
it 'returns the value from some_parent' do
expect(subject.status).to eql('expected status')
end
end
end
It's not necessarily a matter of there being a factual answer to this. How much code coverage you need is up to you. However if we reframe your question as Is there less code coverage when not testing the nil
state? then the answer is yes because they are two different branches and each must be evaluated separately.
When some_parent
is nil
:
some_parent&.calculate_status
# ^ short circuits here and we branch to nil
When some_parent
exists:
some_parent&.calculate_status
# ^ method evaluation occurs and we branch to the method
A simple way to verify this is to test it using simplecov. Here's an example app that can verify it:
# Gemfile
source 'https://rubygems.org'
gem 'rspec'
gem 'simplecov'
gem 'simplecov-console'
# app.rb
# We create the equivalent of your ++belongs_to++ call
# with these classes
class Parent
def calculate_status
"in parent"
end
end
class Child
attr_reader :some_parent
def initialize(parent = nil)
@some_parent = parent
end
def status
@some_parent&.calculate_status
end
end
# app_spec.rb
require "simplecov"
require "simplecov-console"
SimpleCov.start { enable_coverage :branch }
SimpleCov.formatter = SimpleCov::Formatter::Console
require_relative "app"
RSpec.describe "#status" do
context "when some_parent does not exist" do
it "returns nil" do
expect(Child.new.status).to be nil
end
end
context "when some_parent exists" do
it "returns the value from some_parent" do
expect(Child.new(Parent.new).status).to eq("in parent")
end
end
end
Then we run the tests:
bundle exec rspec app_spec.rb
..
Finished in 0.00401 seconds (files took 0.09368 seconds to load)
2 examples, 0 failures
COVERAGE: 100.00% -- 9/9 lines in 1 files
BRANCH COVERAGE: 100.00% -- 2/2 branches in 1 branches
We've tested both branches and have 100% coverage. If we tell it to run only one of the two specs we get a different result:
bundle exec rspec app_spec.rb:14
Run options: include {:locations=>{"./app_spec.rb"=>[14]}}
.
Finished in 0.00141 seconds (files took 0.08878 seconds to load)
1 example, 0 failures
COVERAGE: 88.89% -- 8/9 lines in 1 files
BRANCH COVERAGE: 50.00% -- 1/2 branches in 1 branches
+----------+--------+-------+--------+---------+-----------------+----------+-----------------+------------------+
| coverage | file | lines | missed | missing | branch coverage | branches | branches missed | branches missing |
+----------+--------+-------+--------+---------+-----------------+----------+-----------------+------------------+
| 88.89% | app.rb | 9 | 1 | 3 | 50.00% | 2 | 1 | 15[then] |
+----------+--------+-------+--------+---------+-----------------+----------+-----------------+------------------+
Or the other spec:
bundle exec rspec app_spec.rb:20
Run options: include {:locations=>{"./app_spec.rb"=>[20]}}
.
Finished in 0.00128 seconds (files took 0.08927 seconds to load)
1 example, 0 failures
COVERAGE: 100.00% -- 9/9 lines in 1 files
BRANCH COVERAGE: 50.00% -- 1/2 branches in 1 branches
+----------+--------+-------+--------+---------+-----------------+----------+-----------------+------------------+
| coverage | file | lines | missed | missing | branch coverage | branches | branches missed | branches missing |
+----------+--------+-------+--------+---------+-----------------+----------+-----------------+------------------+
| 100.00% | app.rb | 9 | 0 | | 50.00% | 2 | 1 | 15[else] |
+----------+--------+-------+--------+---------+-----------------+----------+-----------------+------------------+
So if you want full branch coverage then write both specs.