Mocking Functions while Unit Testing

Continuing from the last blog on testing, Here we will see how we can mock call to a function to ensure we test only one function at a time.

Consider the following function, (taken from Post a tweet)

func serveHomePage(w http.ResponseWriter, r *http.Request, cred *oauth.Credentials) {
	if r.URL.Path != "/" {
		http.NotFound(w, r)
		return
	}
	if cred == nil {
		respond(w, homeLoggedOutTmpl, nil)
	} else {
		respond(w, homeTmpl, nil)
	}
}

When writing test case for serveHomePage(), we need to ensure that we do not invoke respond function. Clearly there are different branches in the code above. Lets explore 2 of them

First lets see a branch where URL.Path is not root (“/”).

The test case for the scenario looks like

func TestServeHomePage_PathIsNotRoot(t *testing.T) {
	url := &url.URL{Path : "/dummy"}
	w := &httptest.ResponseRecorder{}
	r := &http.Request {URL: url}
	var cred *oauth.Credentials
	serveHomePage(w, r, cred)
	assert.NotNil(t, w, "We expect something written in Response, but have got nothing")
	assert.Equal(t, w.Header().Get("Content-Type"), "text/plain; charset=utf-8")
	assert.Equal(t, w.Code, 404)
}

Here, we define a url path “/dummy” which ofcourse is different from “/”, We initialize a ResponseRecorder which implements ResponseWriter interface. Further we create a request object with the request url defined earlier.

Now we invoke the method serveHomePage and assert that we get a 404 in response.

Consider another test case, where url path is “/”, but Credentials are not defined, In this case we will end up invoking the respond() function.

Now since our aim is to test serveHomePage(), we must test that and that only and for this purpose, we must mock the respond() function.

However, to mock the respond() method we need to refactor our code. Let us first see the refactored code and the test case, then we will see why we refactored the code that way.

First the refactored serveHomePage() function


type Responder interface {
	respond(w http.ResponseWriter, t *template.Template, data interface{})
}

type Resp struct{ responder Responder }

func (resp *Resp) respond(w http.ResponseWriter, t *template.Template, data interface{}) {
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	if err := t.Execute(w, data); err != nil {
		log.Print(err)
	}
}

func serveHomePage(resp1 Responder,w http.ResponseWriter, r *http.Request, cred *oauth.Credentials) {
	if r.URL.Path != "/" {
		http.NotFound(w, r)
		return
	}
	if cred == nil {
		resp1.respond(w, homeLoggedOutTmpl, nil)
	} else {
		resp1.respond(w, homeTmpl, nil)
	}
}

Note the changed parameters of respond() function

Here, we created an interface Responder which has respond method and a new Type Resp, we implemented respond() method on Type Resp. Further, we updated the signature of serveHomePage() to accept a Responder in the parameter. This update in signature is crucial to mocking the behaviour as we shall see.

Now to take a look at test case

type MockedResp struct{
	mock.Mock
}

func (mockedResp *MockedResp) respond(w http.ResponseWriter, t *template.Template, data interface{}) {
	fmt.Println("In mocked out respond method")
}

func TestServeHomePage_CredentialNotDefined(t *testing.T) {
	url := &url.URL{Path : "/"}
	w := &httptest.ResponseRecorder{}
	r := &http.Request {URL: url}
	var cred *oauth.Credentials
	testObj := new(MockedResp)

	testObj.On("respond", w, nil,nil).Return()
	serveHomePage(testObj, w, r, cred)

	assert.NotNil(t, w, "We expect something written in Response, but have got nothing")
	assert.Equal(t, w.Header().Get("Content-Type"), "")
	assert.Equal(t, w.Code, 0)
}

Here, we created a new type MockedResp and attached a respond() function to it. Since function respond() is defined via interface Responder, mockedResponse implements Responder interface. More on this here. The respond() function described here, overrides the definition.

In the test case we create a “testObj” and tell it, that when respond() function is invoked on the object with those parameters it should return nothing. The behaviour is achieve by this line

testObj.On("respond", w, nil,nil).Return()

Now when we call serveHomePage() from test case and pass testObj in the parameter it invokes the respond() method which is defined on it. In this case, the respond() method defined in the test case.

P.S : I have used the testify library to mock the “mockedResp” type

Let me know, in case you know of better ways of mocking functions in this scenario.

Later!!

~~ Whizdumb ~~
Email : sachin.xpert@gmail.com