4

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.

Vinay
  • 324
  • 1
  • 3
  • 15
  • 1
    Possible duplicate: [Is it possible to mock a function imported from a package in golang?](https://stackoverflow.com/questions/42952191/is-it-possible-to-mock-a-function-imported-from-a-package-in-golang/42952311#42952311) – icza Sep 18 '18 at 07:44
  • @icza That seems to be different. I might be wrong, but as I am not much used to with the Golang, that is why still I am not able to test the helper method. :( ,( – Vinay Sep 18 '18 at 07:53
  • If you mock the function in a way described in the linked answer, you can do anything in your mocked version, e.g. you can increment a counter to see how many times it was called. – icza Sep 18 '18 at 07:55

3 Answers3

7

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
}
Matthew Rudy
  • 16,724
  • 3
  • 46
  • 44
  • How to add this handler to the router then, I'm getting error like - cannot use MyHandler as type http.HandlerFunc – ASHMIL Apr 09 '20 at 07:05
1

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.

Volker
  • 40,468
  • 7
  • 81
  • 87
  • I totally understand your point. But I am stuck in bit odd situation, `notifier.notify` is not making any `database` entry or `http` call, it's kind of `logging` mechanism. So I have no control on it. Am I made myself clear? – Vinay Sep 18 '18 at 08:05
  • 2
    @Vinay Then you cannot test it. If it doesn't have any observable effect one could argue that it doesn't even happen. If your logging system is designed to be not observable than you cannot observe it which means you cannot test it. Some things simply cannot be tested. – Volker Sep 18 '18 at 09:00
1

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)
}
Danny Sullivan
  • 3,626
  • 3
  • 30
  • 39
  • Seems weird to define the mock inside the actual implementation - is that just for illustration or is that how you would actually expect it to be implemented? – Alex Glover Aug 29 '22 at 19:36
  • @AlexGlover Yep, you can (and probably should) have the mock implementation specified in a separate file. Just ensure that the mock implementation implements all of the interface methods so you can inject it as a dependency. – Danny Sullivan Aug 30 '22 at 15:51