Search code examples
ruby-on-railsrspec-railsstate-machine

ArgumentError testing State Machine


I am using the following versions:

ruby 2.5.5

rails 5.2.3

state_machines-activerecord 0.6.0

I have a model Foo that has a state machine on it:

class Foo < ApplicationRecord

  def post_activate
    puts "hello from sunny post_activate"
  end

  state_machine :state, initial: :initial do
    state :active
    after_transition any => :active, :do => :post_activate
    event :activate do
      transition initial: :active
    end
  end
end

I'm trying to write an rspec test to ensure post_activate gets called after the transition to state :active.

I can test ok from the Rails console:

2.5.5 :001 > thinger = Foo.new
 => #<Foo id: nil, state: "initial", created_at: nil, updated_at: nil>
2.5.5 :002 > thinger.activate
   (0.1ms)  begin transaction
  Foo Create (0.4ms)  INSERT INTO "foos" ("state", "created_at", "updated_at") VALUES (?, ?, ?)  [["state", "active"], ["created_at", "2019-06-28 21:35:22.555917"], ["updated_at", "2019-06-28 21:35:22.555917"]]
hello from sunny post_activate
   (0.7ms)  commit transaction
 => true

However, when I run my test:

describe 'Foo' do
  it 'should fire post_activate' do
    foo = Foo.new
    expect(foo).to receive(:post_activate)
    foo.activate
  end
end

I get the following error:

1) Foo should fire post_activate
     Failure/Error: foo.activate

     ArgumentError:
       Wrong number of arguments. Expected 0, got 1.

Interestingly, another test runs correctly with no errors:

it 'should change the state' do
  foo = Foo.new
  expect{ 
    foo.activate
  }.to change {
    foo.state
  }.from('initial').to('active')
end

This test passes and prints my debug message.

I tried putting a parameter into the post_activate definition:

def post_activate(arg)
  puts arg
  puts "hello from sunny post_activate"
end

Now the test passes - and the puts statement reveals that the object passed in is a StateMachines::Transition

#<StateMachines::Transition:0x00007fd5d3534228>
hello from sunny post_activate

I'm not sure how this works - I know how to make a parameter optional, but I don't know how to optionally pass an argument.

In any case - the test passes if I add a parameter to my method definition. But I don't want to add a parameter I'm not using just to get my specs to pass. How do I resolve this?


Solution

  • RSpec verifying proxy in validate_arguments! getting call for post_activate with current transition. You can see it if you add something like byebug to your Gemfile, and add same byebug conditionally into validate_arguments! of Rspec:

    @method_reference.with_signature do |signature|
      verifier = Support::StrictSignatureVerifier.new(signature, actual_args)
      byebug unless verifier.valid?
      raise ArgumentError, verifier.error_message unless verifier.valid?
    

    That will give you access to actual_args:

    (byebug) actual_args
    [#<StateMachines::Transition ...]
    

    So to fix your problem, you just need to add omitted parameter to your callback (as you clearly already figure out):

    def post_activate(_)
    

    And to be sure why underscore used that way, take a look at Ruby Style Guide.