0.0
Repository is archived
No commit activity in last 3 years
No release in over 3 years
Allows parameter passing to implicit RSpec subjects. It also adds support for implicit method calls on described_class instances.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

~> 1.15
~> 10.0
~> 3.0
= 0.49.1

Runtime

 Project Readme

RSpec Arguments CircleCI Gem

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