DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’» is a community of 964,423 amazing developers

We're a place where coders share, stay up-to-date and grow their careers.

Create account Log in
Gilang Anggara
Gilang Anggara

Posted on

Testing A Function That Calls Go Routine

Okay so I've been trying to test a function that calls a routine and I found no solution that fits my taste. This is fairly simple, although some won't like it. Imagine you have a function like this.

package main

import "fmt"

type printer struct{}

func (p printer) HelloWorld() {
  fmt.Println("Hello, world!")
}

type asyncPrinter struct{
  p printer
}

func (ap asyncPrinter) HelloWorld() {
  go ap.p.HelloWorld()
}

Now, if you test this the 'usual' way, it won't wait for the go routine. For example:

package main

import "github.com/stretchr/testify/mock"

type printerMock struct {
  mock.Mock
}

func (pm printerMock) HelloWorld() {
  pm.Called()
}

func Test_AsyncHelloWorld(t *testing.T) {
  pm := printerMock{}
  ap := asyncPrinter(pm)

  pm.On("HelloWorld")
  ap.HelloWorld()

  pm.AssertExpectations(t)
}

This will sometimes fail, the other time will pass. To 'trick' it some will suggest waiting in test. For examples:

package main

import (
  "time"

  "github.com/stretchr/testify/mock"
)

type printerMock struct {
  mock.Mock
}

func (pm printerMock) HelloWorld() {
  pm.Called()
}

func Test_AsyncHelloWorld(t *testing.T) {
  pm := printerMock{}
  ap := asyncPrinter(pm)

  pm.On("HelloWorld")
  ap.HelloWorld()

  time.Sleep(1 * time.Second)
  pm.AssertExpectations(t)
}

Yes, it works! But we don't know how long we should wait till the go routine finish. The way that I like to do it is to use wait group and help of test variable. Take a look at the code:

package main

import (
  "fmt"
  "sync"
)

var testMode bool
var wg sync.WaitGroup

type printer struct{}

func (p printer) HelloWorld() {
  fmt.Println("Hello, world!")
}

type asyncPrinter struct{
  p printer
}

func (ap asyncPrinter) HelloWorld() {
  if testMode {
    wg.Add(1)
  }
  go ap.p.HelloWorld()
  if testMode {
    wg.Wait()
  }
}

This way, we can now simply write test that calls wg.Done() on method call. For examples:

package main

import (
  "sync"

  "github.com/stretchr/testify/mock"
)

type printerMock struct {
  mock.Mock
}

func (pm printerMock) HelloWorld() {
}

func Test_AsyncHelloWorld(t *testing.T) {
  pm := printerMock{}
  ap := asyncPrinter(pm)

  testMode = true
  pm.On("HelloWorld").Run(func(){
    wg.Done()
  })
  ap.HelloWorld()

  pm.AssertExpectations(t)
}

Voila! You don't have to worry about slow test because of time.Sleep()!

DISCLAIMER: I haven't run code written here, you might need a little bit adjustment.

Top comments (1)

Collapse
 
nurintaaan profile image
Nur Intan Alatas

Cool

Take a look at this:

Settings

Go to your customization settings to nudge your home feed to show content more relevant to your developer experience level. πŸ›