Go goroutine test failing Expected number of calls

2.7k views Asked by At

I'm new to Go here. I am trying to test the function call inside my Go routine but it fails with the error message

Expected number of calls (8) does not match the actual number of calls (0).

My test code goes like:

package executor

import (
    "testing"
  "sync"
    "github.com/stretchr/testify/mock"
)

type MockExecutor struct {
    mock.Mock
  wg sync.WaitGroup
}

func (m *MockExecutor) Execute() {
  defer m.wg.Done()
}

func TestScheduleWorksAsExpected(t *testing.T) {

    scheduler := GetScheduler()
    executor := &MockExecutor{}
    scheduler.AddExecutor(executor)

    // Mock exptectations
    executor.On("Execute").Return()
    

    // Function Call
  executor.wg.Add(8)
    scheduler.Schedule(2, 1, 4)
  executor.wg.Wait()

  executor.AssertNumberOfCalls(t, "Execute", 8)

}

and my application code is:

package executor

import (
    "sync"
    "time"
)

type Scheduler interface {
    Schedule(repeatRuns uint16, coolDown uint8, parallelRuns uint64)
    AddExecutor(executor Executor)
}

type RepeatScheduler struct {
    executor  Executor
    waitGroup sync.WaitGroup
}

func GetScheduler() Scheduler {
    return &RepeatScheduler{}
}

func (r *RepeatScheduler) singleRun() {
    defer r.waitGroup.Done()
    r.executor.Execute()
}

func (r *RepeatScheduler) AddExecutor(executor Executor) {
    r.executor = executor
}

func (r *RepeatScheduler) repeatRuns(parallelRuns uint64) {
    for count := 0; count < int(parallelRuns); count += 1 {
        r.waitGroup.Add(1)
        go r.singleRun()
    }

    r.waitGroup.Wait()
}

func (r *RepeatScheduler) Schedule(repeatRuns uint16, coolDown uint8, parallelRuns uint64) {

    for repeats := 0; repeats < int(repeatRuns); repeats += 1 {
        r.repeatRuns(parallelRuns)
        time.Sleep(time.Duration(coolDown))
    }

}

Could you point out to me what I could be doing wrong here? I'm using Go 1.16.3. When I debug my code, I can see the Execute() function being called but testify is not able to register the function call

1

There are 1 answers

1
Brits On BEST ANSWER

You need to call Called() so that mock.Mock records the fact that Execute() has been called. As you are not worried about arguments or return values the following should resolve your issue:

func (m *MockExecutor) Execute() {
    defer m.wg.Done()
    m.Called()
}

However I note that the way your test is currently written this test may not accomplish what you want. This is because:

  • you are calling executor.wg.Wait() (which will wait until the function has been called the expected number of times) before calling executor.AssertNumberOfCalls so your test will never complete if Execute() is not called at least the expected number of times (wg.Wait() will block forever).
  • After m.Called() has been called the expected number of times there is a race condition (if executor is still be running there is a race between executor.AssertNumberOfCalls and the next m.Called()). If wg.Done() does get called an extra time you will get a panic (which I guess you could consider a fail!) but I'd probably simplify the test a bit:
scheduler.Schedule(2, 1, 4)
time.Sleep(time.Millisecond) // Wait long enough that all executions are guaranteed to have completed (should be quick as Schedule waits for go routines to end)
executor.AssertNumberOfCalls(t, "Execute", 8)