Rspec 3.9 user input while loop tests

92 views
Skip to first unread message

Dmitry Sokolyansky

unread,
Feb 5, 2020, 8:53:42 AM2/5/20
to rspec
I'm trying to test a "console" method that switches states. Each state has user input.  
I have test example on github https://github.com/dmitriy-sokolyanskiy/ref 
Desired behavior: in test I expect the "console" method to be launched  with initial `@state = :greeting` and switching to `@state = :base_option`, which asks for user input, for example, `"create"`, which switches to `state = :main_menu`, which requires user input, for example, an `“exit”` that leads to an exit.  

   
    def console
     loop
do
     
break unless STATES.include?(@state)

      public_send
(@state)
     
end
   
end

   
def greeting
      message
(:greeting)
     
@state = :base_option
   
end


    
Problems:

 1. it does not execute console input since `@states` does not change correctly. 
 2. `expect(current_subject).to receive(:create)`  - is not met.    

 
let(:current_subject) { described_class.new }
let(:input_sequence) { %w[create exit] }

describe '#console' do
  context 'when correct method calling' do
    after do
      # @state=:greeting
      current_subject.console
     
# @state=nil
    end

    it 'create account if input is create' do
      allow(current_subject).to receive_message_chain(:gets, :chomp) { input_sequence }

      expect
(current_subject).to receive(:create)
   
end
  end
end

         
I'm stuck. Goodle doesn't know about it too (

Jon Rowe

unread,
Feb 5, 2020, 9:16:18 AM2/5/20
to rs...@googlegroups.com
Hello!

You’ve not really provided enough code to be able to help entirely, but for certain I can say you have a problem in your spec.

By calling `after` you are never executing your `current_subject.console` during your test. So the `expect(current_subject).to receive(:create)` will always fail your test.

The fact that your test is exiting indicates that the allow is working fine and its progressing through your console input, as otherwise that loop would never exit and your test would hang.

Personally this is not how I would test this. Instead I would refactor the code so that loop causes a step change to occur e.g:

```
def console
  loop { process_state(@state) }
end

def process_state(state)
  exit unless STATES.include state
  public_send(state)
end
```

I would then test process_state in what ever transition you would want, this avoids the loop. Just make sure you are calling the methods required in your test and not in `after.

Cheers
Jon Rowe
---------------------------

Dmitry Sokolyansky

unread,
Feb 5, 2020, 12:36:58 PM2/5/20
to rspec
Thanks a lot Jon. I've refactored the loop and the tests began to work.
But there is one more problem. The test does not reach the expect operator. This is visible if you replace the method with one that will not be called, e.g. "load"
     
     
      it 'create account if input is create' do

        allow
(current_subject).to receive_message_chain(:gets, :chomp).and_return(*input_sequence)  
       
# current_subject.console
        expect
(current_subject).to receive(:load)
        current_subject
.console
     
end


The test does not reach the “expect” operator, nether if current_subject.console is after nor before the one.
The loop is not infinite. The exit from the loop occurs. I checked this by the displayed messages and the status value.

Jon Rowe

unread,
Feb 5, 2020, 1:19:19 PM2/5/20
to rs...@googlegroups.com
Whats the error you are seeing?

Jon Rowe
---------------------------

Dmitry Sokolyansky

unread,
Feb 5, 2020, 1:43:03 PM2/5/20
to rspec
The test is green, it passes, but it should fail because it expects to start a method that is not called, in this case the “load” method.

Jon Rowe

unread,
Feb 5, 2020, 3:40:45 PM2/5/20
to rs...@googlegroups.com
The code in your repo fails correctly, is it possible you’ve commented out something or are actually calling the method? I can assure you the expectation works correctly!

Cheers
Jon Rowe
---------------------------

Dmitry Sokolyansky

unread,
Feb 6, 2020, 1:38:35 AM2/6/20
to rspec
You just haven't switched to the "dev" branch where these changes are located. In the "master" branch, I saved the previous code for comparison.
In the last link, its ending (... / tree / dev) points to the "dev" branch.
Unfortunately, I can’t change the top link that points to the “master” branch.
To avoid confusion, I merged the “dev” branch into “master”.

Dmitry Sokolyansky

unread,
Feb 6, 2020, 5:54:15 AM2/6/20
to rspec
I figure out. The thread does not go to the "expect" statement because of the Ruby script terminates with the "exit" statement.
So, I reverted PR, and now in the “master” branch is the implementation of my loop, and in the “dev” branch is the implementation with the “exit” operator.

My loop implementation works well, but “expect (current_subject) .to receive (: create)” runs the “create” method in addition to the loop and it hangs up the test.
If you comment out the “expect” statement, the test completes correctly.
I mean going through all the expected methods that I checked by their messages and state values, that is, I see changes of all expected states:
:greeting -> :choose_base_option -> :create -> :exit

 And if you change the name of the called method from "create" to "load" (which will not be called), the test will fail, as expected.
If you add "once / twice"  to the “expect” expression, for example, “expect(current_subject).to receive(:create).once / twice ”, this will raise an expectation error

(#<Console:0x0000563bfc2f7f90 @state=:create>).create(no args)
           expected
: 1 time with any arguments
           received
: 2 times
and
(#<Console:0x00005570a26aedf8 @state=:create>).create(no args)
           expected
: 2 times with any arguments
           received
: 3 times
respectively
branch master

Thus, the issue is how to test the method being called without calling it from the expression "expect".

Jon Rowe

unread,
Feb 7, 2020, 5:21:25 AM2/7/20
to rs...@googlegroups.com
Hi Dimitry

Ah yes, we don’t rescue `exit` as we consider that to be the correct Ruby semantic, we don’t know wether its your code, an extension, or some other reason, you can prevent it yourself with `expect { … }.to raise_error(SystemExit)`.

`expect(something).to receive(:message)`

Will not invoke `message` on `something` unless you use `.and_call_original()`. It replaces it with a dummy implementation that returns nil by default.

Cheers
Jon Rowe
---------------------------

Dmitry Sokolyansky

unread,
Feb 8, 2020, 4:16:59 AM2/8/20
to rspec
Hi, Jon

It works. Thanks a lot.

Cheers
Dmitriy Sokolyanskiy
Reply all
Reply to author
Forward
0 new messages