Search code examples
unit-testinggo

Golang Issue with Unit Testing for simple error


I am having trouble trying to test this function with an error inside. The following is my ResponseJson function which does not return an error but sends a response json.

func ResponseJson(w http.ResponseWriter, Code int, Message string) {
    jsonStatus := struct {
        Code int `json:"code"`
        Message string `json:"message"`
    }{
        Message: Message,
        Code: Code,
    }

    bs, err := json.Marshal(jsonStatus);

    if err != nil {
        log.Println("Error in Marshal JSON in ResponseJson: ", err)
        str := "Internal Server Error. Please contact the System Administrator."
        io.WriteString(w, str);
        return 
    } else {
        io.WriteString(w, string(bs));
        return
    }
    
}

The following is my unit testing code which creates a mock ResponseWriter and it is able to successfully test the writer response json for cases with no errors. Since I am not returning an error type in ResponseJson() function, how do I test it inside the Test_ResponseJson function as shown below?

func Test_ResponseJson(t *testing.T) {
    responseJsonTests := []struct {
        testName string
        code int
        message string
        expectedJsonResponse string
    } {
        {"Successful Login", http.StatusOK, "Successfully Logged In!", `{"code":200,"message":"Successfully Logged In!"}`},
        {"Existing username", http.StatusBadRequest, "Username already exists. Please try again.", `{"code":400,"message":"Username already exists. Please try again."}`},
    }

    for _, e := range responseJsonTests {
        // Creating a mock ResponseWriter
        w := httptest.NewRecorder()

        ResponseJson(w, e.code, e.message)

        // Read the response body as a string
        body, _ := io.ReadAll(w.Result().Body)
        actual := string(body)

        expected := e.expectedJsonResponse
        if actual != expected {
            t.Errorf("%s: expected %s but got %s", e.testName, e.expectedJsonResponse, actual)
        }
    }
}

Also, I have created a function which generates an actual log output for log.Println() built-in function. I am aware that the log.Println() function is a built-in function and it is highly unlikely to fail. However, I want to achieve 100% coverage in my unit testing.

func GenerateLogOutput(message string, errorMessage string) string {
    // Create a new bytes.Buffer to capture the log output
    var buf bytes.Buffer

    // Redirect log output to a different destination set as a buffer
    // By default, log message are written to the standard error stream os.Stderr
    log.SetOutput(&buf)

    // Generate an error
    err := errors.New(errorMessage)

    w := httptest.NewRecorder()

    // Calling the function
    InternalServerError(w, message, err)
    actualOutput := buf.String()

    return actualOutput
}

Solution

  • Simply, We can write a test case for the ResponseJson function as below.

     func Test_ResponseJson(t *testing.T) {
        tests := []struct {
            Code        int
            Message     string
            ExpectedStr string
        }{
            {
                Code:        1,
                Message:     "sample message",
                ExpectedStr: "{\"code\":1,\"message\":\"sample message\"}",
            },
        }
        for _, test := range tests {
            w := httptest.NewRecorder()
    
            ResponseJson(w, test.Code, test.Message)
    
            res := w.Result()
            data, err := ioutil.ReadAll(res.Body)
            res.Body.Close()
    
            actualStr := string(data)
    
            assert.Nil(t, err, "Invalid test data")
            assert.Equal(t, actualStr, test.ExpectedStr)
        }
    }
    

    We cannot get an error from bs, err := json.Marshal(jsonStatus). The json.Marshal function can return two types of errors.

    1. UnsupportedTypeError (ex: channel, complex, and function values)
    2. UnsupportedValueError (ex: cyclic data structures)

    We cannot parse values to generate either one of the above errors. We are parsing a struct with supported values and supported types. Therefore, we cannot write tests with 100% coverage.