Testing Workflow that waits for Signal

Hi wrote a simple workflow to simulate my real workflow that is either waits for a signal or times out by timer.
I would appreciate a test example on how to test this code, the examples you have always rely on timer code (samples-go: mutex).
Also if possible please publish a link to documentation on how to do Integration Tests with Temporal.

Here is my workflow

func Workflow(ctx workflow.Context) (value string,err error) {
	selector := workflow.NewSelector(ctx)

	selector.AddReceive(workflow.GetSignalChannel(ctx, "signal-name"),func (c workflow.ReceiveChannel, _ bool){
		c.Receive(ctx, &value)
	})
	selector.AddFuture(workflow.NewTimer(ctx, time.Second), func(f workflow.Future) {
		err = fmt.Errorf("timeout reached")
	})
	selector.Select(ctx)
	return
}

If I write a test this way

func (s *fakeSuite) TestSignalFirst() {
	s.env.ExecuteWorkflow(Workflow)
	s.env.SignalWorkflow("signal-name", "signal-value")
	s.NoError(s.env.GetWorkflowError())
	s.True(s.env.IsWorkflowCompleted())
	var result string
	s.env.GetWorkflowResult(&result)
	s.Equal("signal-value", result )
}

The code that should act on Signal is not run since the Timer is fired immediately after I ExecuteWorkflow and I receive a timeout message

2020/11/19 19:00:49 DEBUG Auto fire timer TimerID 1 TimerDuration 1s TimeSkipped 1s
    fake_test.go:30: 
        	Error Trace:	fake_test.go:30
        	Error:      	Received unexpected error:
        	            	workflow execution error (type: Workflow, workflowID: default-test-workflow-id, runID: default-test-run-id): timeout reached
        	Test:       	TestFake/TestSignalFirst

I tried:

  • Signal first, Execute later gives me a nil panic.
  • SignalWorkflowSkippingWorkflowTask + SignalWorkflow gives me nil panic
  • RegisterDelayedCallback with a delay, it delays then timeout is reached

Please advise

1 Like

Send signal from the delayed callback registered through RegisterDelayedCallback. I’m pretty sure it works. If you cannot make it work give us a reproduction of the issue.

Following from Maxim’s reply, here’s a working example: https://github.com/Courtsite/temporal-go-helpers/blob/master/channel/signal_test.go#L23

Regarding integration testing, it really depends. For example, if you are referring to testing activities which talks to say HTTP APIs, you can use https://github.com/dnaeon/go-vcr or similar for capturing data from real backends. Alternatively, you can use Go’s built-in httptest.NewServer.

1 Like

Thank you for your replies, just to make sure we all on same page here:

I want to test the code that acts on Signal Receive first - before the timer fires.

Here is the complete example

Here you can find what I tried/did

This one panic since the Workflow isn’t complete

func (s *fakeSuite) TestSignalFirst() {
	s.env.RegisterDelayedCallback(func() {}, time.Minute)
	s.env.SignalWorkflow("signal-name", "signal-value")
	s.NoError(s.env.GetWorkflowError())
	s.True(s.env.IsWorkflowCompleted())
	var result string
	s.env.GetWorkflowResult(&result)
	s.Equal("signal-value", result )
}

Output:

    fake_test.go:36: 
        	Error Trace:	fake_test.go:36
        	Error:      	Should be true
        	Test:       	TestFake/TestSignalFirst
    suite.go:63: test panicked: workflow is not completed

Another failing example, fails on timer instead of waiting.

func (s *fakeSuite) TestSignalFirst() {
	s.env.RegisterDelayedCallback(func() {}, time.Minute)
	//s.env.SignalWorkflow("signal-name", "signal-value")
	s.env.ExecuteWorkflow(Workflow)
	s.NoError(s.env.GetWorkflowError())
	s.True(s.env.IsWorkflowCompleted())
	var result string
	s.env.GetWorkflowResult(&result)
	s.Equal("signal-value", result )
}

Output

2020/11/21 09:25:01 DEBUG Auto fire timer TimerID 2 TimerDuration 1s TimeSkipped 1s
    fake_test.go:32: 
        	Error Trace:	fake_test.go:32
        	Error:      	Received unexpected error:
        	            	workflow execution error (type: Workflow, workflowID: default-test-workflow-id, runID: default-test-run-id): timeout reached
        	Test:       	TestFake/TestSignalFirst
    fake_test.go:36: 
        	Error Trace:	fake_test.go:36
        	Error:      	Not equal: 
        	            	expected: "signal-value"
        	            	actual  : ""
        	            	
        	            	Diff:
        	            	--- Expected
        	            	+++ Actual
        	            	@@ -1 +1 @@
        	            	-signal-value
        	            	+
        	Test:       	TestFake/TestSignalFirst
--- FAIL: TestFake (0.00s)
    --- FAIL: TestFake/TestSignalFirst (0.00s)

RegisterDelayedCallback registers a callback to fire after a delay. The call itself is not blocking. Try changing it to:

s.env.RegisterDelayedCallback(func() {
    s.env.SignalWorkflow("signal-name", "signal-value")
}, time.Minute)	

Also, you probably want to adjust the timer from one-second delay if you want to fire it after the signal.

Thanks, that does work as expected.
Here is a working example:

func (s *fakeSuite) TestSignalFirst() {
	s.env.RegisterDelayedCallback(func() {
		s.env.SignalWorkflow("signal-name", "signal-value")
	}, time.Millisecond)
	s.env.ExecuteWorkflow(Workflow)
	s.NoError(s.env.GetWorkflowError())
	s.True(s.env.IsWorkflowCompleted())
	var result string
	s.env.GetWorkflowResult(&result)
	s.Equal("signal-value", result )
}

Note that the TestWorkflowEnvironment performs automatic time skipping when a workflow is waiting. So no need to specify so short timeouts. You could specify hour delay and two-hour sleep and the unit test still would execute in milliseconds.