Test that method is called in handler in Golang

Viewed 7665

I am implementing a API in Golang. I have a endpoint where I am calling a method with parameters of other package. Now I need to check that, that method has been called in the request.

Below is the small similar scenario what I am doing and what I am expecting.

My handler

package myPackage
import (
    "log"
    "github.com/myrepo/notifier" // my another package
)

func MyHandler(writer http.ResponseWriter, request *http.Request) { 
    // ...
    // ...

    notifier.Notify(4, "sdfsdf")

    // ...
    // ...

}

Testing handler

func TestMyHandler(t *testing.T) {
    // Here I want to 
    req, _ := http.NewRequest("GET", "/myendpoint", nil)
    // ... Want to test that notifier.Notify is called
    // ...
}

In the TestMyHandler, I want to check that notifier.Notify has called.

Findings

I tried to understand the AssertNumberOfCalls, func (*Mock) Called, and func (*Mock) MethodCalled but I am not sure how to use them :(.

I am a newbie in Golang and really exicted to do that. Please let me know if I missed anything or you may need more information for more understaing.

3 Answers

This is a good opportunity to use dependency injection and interfaces.

Namely, we need to extract the concept of a Notifier

(warning: code not tested directly)

type Notifier interface {
    Notify(int, string)() error
}

Now to avoid any confusion with the notifier library, use a local alias.

import "github.com/myrepo/notifier" mynotifier

Then, because the library you're using exports it as a function, not within a struct, we'll need to make a struct that implements our interface

type myNotifier struct {}

func (mn *myNotifier) Notify(n int, message string) error {
  return mynotifier.Notify(n, message)
}

Then you modify your function:

func MyHandler(writer http.ResponseWriter, request *http.Request, notifier Notifier) { 
    // ...
    // ...

    notifier.Notify(4, "sdfsdf")

    // ...
    // ...

}

Then in your test, you're now free to send in a spy Notifier

type spyNotifier struct {
  called boolean
}

func (n *spyNotifier) Notify(n int, msg string) error {
  n.called = true
  return
}

Want to test that notifier.Notify is called.

No you don't. You are interested in that the handler does what it should do and this seems to consist of two things:

  1. Return the right response (easy to test with a net/http/httptest.ResponseRecorder), and

  2. Has some noticeable side effect, here issue some notification.

To test 2. you test that the notification was issued, not that some function was called. Whatever notify.Notify results in (e.g. a database entry, a file, some HTTP call) should be tested. Formally this is no longer unit testing but testing for side effects is never strict unit testing.

What you can do: Wrap your handler logic into some object and observe that objects state. Ugly. Don't.

This approach is similar to Mathew's answer, but uses the mock package from testify instead. Here, you create a mock implementation of the Notifier, register the method call, and assert that the method has been called with the expected arguments.

Implementation

package handler

import (
    "net/http"
    "github.com/stretchr/testify/mock"
)

// the interface for the Notifier
type Notifier interface {
  Notify(int, string) error
}

// the mock implementation of the interface above
type MockNotifier struct {
  mock.Mock
}

// stub the notify method to ensure it can be expected later in the test
func (mockNotifier *MockNotifier) Notify(arg1 int, arg2 string) error {
    args := mockNotifier.Called(arg1, arg2)

    return args.Error(0)
}

// this handler which accepts a Notifier for dependency injection
type Handler struct {
  notifier Notifier
}

// the MyHandler implementation which calls the notifier instance
func (h *Handler) MyHandler(writer http.ResponseWriter, request *http.Request) {
  // this is what we want to test!
  h.notifier.Notify(4, "sdfsdf")
}

Test

package handler_test

import (
    "net/http"
    "net/http/httptest"
    "testing"
)

func TestMyHandler(t *testing.T) {
  t.Parallel()

  mockNotifier := MockNotifier{}
  handler := Handler{ notifier: &mockNotifier }

  // register the mock to expect a call to Notify with the given arguments and return nil as the error
  mockNotifier.On("Notify", 4, "sdfsdf").Return(nil)

  // setup the test arguments
  request := httptest.NewRequest(http.MethodGet, "/someapi", nil)
  writer := httptest.NewRecorder()

  // call the handler
  handler.MyHandler(writer, request)

  // this is the important part!!
  // this ensures that the mock Notify method was called with the correct arguments, otherwise the test will fail
  mockNotifier.AssertExpectations(t)
}
Related