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
endInstall
Add the following to your Gemfile:
group :test do
# ...
gem 'rspec-arguments'
# ...
endExecute the following command:
bundle installDocumentation
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) }
doThis 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) }
doNow 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) }
endIn 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
endTraditionally, 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
endNotice 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
endNotice 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
endRSpec.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