testing processes

A few notes about testing/specing process definitions

The dir:“https://github.com/jmettraux/ruote/tree/master/test/functional” is full of process definitions tortured by tests, but each of them is very technical.

#wait_for(x)

This dashboard (engine) method is heavily used to make the test thread wait for the engine (worker) thread.

It’s documented there

Note: wait_for is not meant to be used outside tests, unless in some special cases.

the bare way

Testing a process definition without a test framework. Useful when demonstrating a ruote feature or making sure that this feature works for a person (I use this way all the time on the mailing list).

            require 'rubygems'
            require 'ruote' # gem install ruote
            
            engine = Ruote::Engine.new(Ruote::Worker.new(Ruote::HashStorage.new()))
              # an engine + worker combo with an in-memory storage,
              # ideal for process definition testing
            
            #engine.noisy = true
              # when the engine/worker is not replying, uncomment this and restart the
              # test, it the worker will then explain its activity in yellow chars
              # in your console
            
            engine.register do
              catchall
            end
            
            pdef = Ruote.process_definition do
              alpha
              bravo
            end
            
            wfid = engine.launch(pdef, { 'colour' => 'blue' })
              # launching an instance of pdef, with a workitem whose initial fields
              # contain 'colour' => 'blue'
              #
              # we keep the process instance id in the variable 'wfid' (workflow
              # instance id)
            
            engine.wait_for(:alpha)
              # waiting for a participant name as a symbol, this call will block until
              # a workitem was delivered to participant 'alpha'
            
            workitem = engine.storage_participant.first
              # grab the workitem from the storage participant
            
            #workitem.fields['colour'] = 'red'
              # maybe alter the payload of the workitem
            
            engine.storage_participant.reply(workitem)
              # hands back the workitem to the storage participant, it will erase it
              # and hand it back to the engine so that the flow may resume
            
            engine.wait_for(:bravo)
              # waiting for flow to pop a workitem up at the next step
            
            workitem = engine.storage_participant.first
              # grab the workitem from the storage participant
            
            engine.storage_participant.reply(workitem)
            
            engine.wait_for(wfid)
              # wait for our process to terminate (or to issue an error)
            
            raise "process not over" if engine.process(wfid)
            

test/unit way

TODO

rspec way

An example spec, it instantiates a new dashboard with a worker and an in-memory storage each time. The participant is a simple catchall, that bombs if the workitem field ‘fail’ is true-ish.

            require 'ruote'
            
            
            describe 'my freaking process' do
            
              before 'each' do
            
                @board = Ruote::Dashboard.new(Ruote::Worker.new(Ruote::HashStorage.new))
            
                @board.register '.+' do |workitem|
                  pname = workitem.participant_name
                  fail 'doom' if pname != 'charly' && workitem.fields['fail']
                  workitem.fields[workitem.participant_name] = 'was here'
                end
              end
            
              after 'each' do
            
                @board.shutdown
              end
            
              let(:definition) do
                Ruote.define :on_error => 'charly' do
                  alpha
                  bravo
                end
              end
            
              # the specs...
            
              context 'happy path' do
            
                it 'completes successfully' do
            
                  wfid = @board.launch(definition)
            
                  r = @board.wait_for(wfid)
                    # wait until process terminates or hits an error
            
                  r['workitem'].should_not == nil
                  r['workitem']['fields']['alpha'].should == 'was here'
                  r['workitem']['fields']['bravo'].should == 'was here'
                  r['workitem']['fields']['charly'].should == nil
                end
              end
            
              context 'unhappy path' do
            
                it 'routes to charly' do
            
                  initial_workitem_fields = { 'fail' => true }
            
                  wfid = @board.launch(definition, initial_workitem_fields)
            
                  r = @board.wait_for(wfid)
                    # wait until process terminates or hits an error
            
                  r['workitem'].should_not == nil
                  r['workitem']['fields']['alpha'].should == nil
                  r['workitem']['fields']['bravo'].should == nil
                  r['workitem']['fields']['charly'].should == 'was here'
                end
              end
            end
            

cucumber way

ruote-cukes is a set of step definitions for testing ruote process definitions from Cucumber, unfortunately, it’s not complete.