Provide arguments to the implicit RSpec subject
.
Also, call instance and class methods implicitly.
Example (TL;DR)
class Thing
def initializer(username)
end
def perform(save:)
save
end
end
RSpec.describe Thing do
arg(:username, 0) { 'user123' }
it { is_expected.to be_a(Thing) }
describe '#perform', :method do
method_arg(:save) { true }
it { is_expected.to eq(save) }
it 'should be able to access the class instance' do
expect(instance.perform(save: save)).to eq(subject)
end
end
end
Install
Add the following to your Gemfile
:
group :test do
# ...
gem 'rspec-arguments'
# ...
end
Execute the following command:
bundle install
Documentation
Out-of-the box, RSpec provides us with an implicit subject
method that instantiates the described class under test, giving us an instance which we can assert on:
class User
end
RSpec.describe User do
it { is_expected.to be_a(User) }
do
This is very terse and works great for classes with no initialization parameters.
But we can't use the implicit subject
when initialization parameters need to be provided.
class User
def initialize(name, tag:)
# ...
end
end
RSpec.describe User do
let(:name) { 'Eva' }
let(:tag) { :mobile }
subject { described_class.new(name, tag: tag) }
it { is_expected.to be_a(User) }
do
Now you have to explicitly declare your subject
, proving the required parameters to the initializer.
Having parameters in initializers is not uncommon.
This gem provides new methods that allow you to implicitly provide initializer arguments to the implicit subject
:
RSpec.describe User do
arg(:name, 0) { 'Eva' }
arg(:tag) { :mobile }
# Translates to:
# subject { described_class.new(name, tag: tag) }
it { is_expected.to be_a(User) }
end
In this example, :name
is an initializer positional argument, at position 0
, and :tag
is the keyword argument with the same symbol.
The interesting part happens when we want to call a method from the class instance under test, a very common use case.
To illustrate this, let's add a new method save
to the class User
:
class User
def initialize(name, tag:)
# ...
end
def save(validate, touch)
# ...
return validate
end
end
Traditionally, we could test it as such:
RSpec.describe User do
arg(:name, 0) { 'Eva' }
arg(:tag) { :mobile }
it { is_expected.to be_a(User) }
describe '#save' do
let(:validate) { false }
let(:touch) { true }
subject { described_class.new(name, tag: tag).save(validate, touch) }
it { is_expected.to eq(validate) }
end
end
Notice we can't reuse our implicit subject
, and have to resort to re-initializing our described_class
, and proving the required arguments to the desired method.
Similarly to initializer methods, this gem introduces methods to facilitate implicit method calls.
RSpec.describe User do
arg(:name, 0) { 'Eva' }
arg(:tag) { :mobile }
it { is_expected.to be_a(User) }
describe '#save', :method do
method_arg(:validate, 0) { false }
method_arg(:touch, 1) { true }
# Translates to:
# subject { described_class.new(name, tag: tag).save(validate, touch) }
it { is_expected.to eq(validate) }
end
end
Notice that we don't have to repeat ourselves on what method needs to be tested, save
in this case, as we can infer it from the describe '#method_name', :method do
context.
Lastly, here's a full example, including methods requiring &block
arguments, and class method calls:
class User
def initialize(name, tag:)
# ...
end
def save(validate, touch)
# ...
return validate
end
def self.find_all(&block)
# ...
block.call
end
end
RSpec.describe User do
arg(:name, 0) { 'Eva' }
arg(:tag) { :mobile }
it { is_expected.to be_a(User) }
describe '#save', :method do
method_arg(:validate, 0) { false }
method_arg(:touch, 1) { true }
it { is_expected.to eq(validate) }
end
context '.find_all', :class_method do
method_arg_block(:block) { proc { 1 } }
it { is_expected.to eq(1) }
end
end