How do I create a mock instance of http.ResponseWriter that returns an error on responseWriter.Write?

huangapple go评论83阅读模式
英文:

How do I create a mock instance of http.ResponseWriter that returns an error on responseWriter.Write?

问题

我将所有与响应相关的Go代码移入了一个函数中:

import (
	"encoding/json"
	"net/http"

	"github.com/rs/zerolog"
)

func WriteResponse(responseWriter http.ResponseWriter, responseBody any, httpStatusCode int) {
	encodedResponseBody, err := json.Marshal(responseBody)

	if err != nil {
		// 调用日志记录器

		apiResponse := NewErrorApiResponse() // 为HTTP 500响应创建一个结构体
		encodedResponseBody, err = json.Marshal(apiResponse)

		responseWriter.WriteHeader(http.StatusInternalServerError)

		if err != nil {
			// 调用日志记录器

			return
		}

		_, err = responseWriter.Write(encodedResponseBody)

		if err != nil {
			// 调用日志记录器
		}

		return
	}

	responseWriter.WriteHeader(httpStatusCode)
	_, err = responseWriter.Write(encodedResponseBody)

	if err != nil {
		// 调用日志记录器
	}
}

并为以下情况创建了测试:

  • 成功
  • 错误 => json.Marshal() 失败,但 responseWriter.Write() 没有失败

现在我还想为以下情况创建一个测试:

json.Marshal() 失败,它尝试创建一个错误响应,但 responseWriter.Write() 也失败了,所以我们只能记录错误

所以基本上我从这里开始:

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
	"testing"

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

func TestWriteResponse_NoResponseBody(testing *testing.T) {
	// TODO 使用返回.Write()错误的模拟替代
	responseWriter := httptest.NewRecorder()
	responseBody := make(chan int)

	WriteResponse(responseWriter, responseBody, http.StatusOK)

	assert.Equal(testing, http.StatusInternalServerError, responseWriter.Code, fmt.Sprintf("期望状态码为%d,但得到%d", http.StatusInternalServerError, responseWriter.Code))

	actualResponseBodyAsString := responseWriter.Body.String()

	assert.Equal(testing, "", actualResponseBodyAsString, fmt.Sprintf("期望响应体为空,但得到'%s'", actualResponseBodyAsString))
}

但是测试失败,因为 responseWriter.Write() 正常工作。我该如何强制它返回一个错误,以便响应体为空?

英文:

I moved all the response related Go code into a function

import (
	"encoding/json"
	"net/http"

	"github.com/rs/zerolog"
)

func WriteResponse(responseWriter http.ResponseWriter, responseBody any, httpStatusCode int) {
	encodedResponseBody, err := json.Marshal(responseBody)

	if err != nil {
		// call logger

		apiResponse := NewErrorApiResponse() // create a struct for HTTP 500 responses
		encodedResponseBody, err = json.Marshal(apiResponse)

		responseWriter.WriteHeader(http.StatusInternalServerError)

		if err != nil {
			// call logger

			return
		}

		_, err = responseWriter.Write(encodedResponseBody)

		if err != nil {
			// call logger
		}

		return
	}

	responseWriter.WriteHeader(httpStatusCode)
	_, err = responseWriter.Write(encodedResponseBody)

	if err != nil {
		// call logger
	}
}

and created tests for the following cases

  • Success
  • Error => json.Marshal() failed but not responseWriter.Write()

now I also want to create a test for the following case

> json.Marshal() failed, it tried to create an error response but responseWriter.Write() too so we can only log the error

So basically I started with

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
	"testing"

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

func TestWriteResponse_NoResponseBody(testing *testing.T) {
	// TODO replace with mock returning error on .Write()
	responseWriter := httptest.NewRecorder()
	responseBody := make(chan int)

	WriteResponse(responseWriter, responseBody, http.StatusOK)

	assert.Equal(testing, http.StatusInternalServerError, responseWriter.Code, fmt.Sprintf("Expected status code %d, but got %d", http.StatusInternalServerError, responseWriter.Code))

	actualResponseBodyAsString := responseWriter.Body.String()

	assert.Equal(testing, "", actualResponseBodyAsString, fmt.Sprintf("Expected response body to be empty but got '%s'", actualResponseBodyAsString))
}

but the test fails because responseWriter.Write() is working fine. How do I force it to return an error so the response body will be empty?

答案1

得分: 4

只需实现以下接口:

type ResponseWriter interface {
    Header() Header
    Write([]byte) (int, error)
    WriteHeader(statusCode int)
}

所以可以这样实现:

type ErrorMockResponseWriter struct {
    Body io.ReadCloser
    Code int
}

func (e *ErrorMockResponseWriter) Header() http.Header {
    return http.Header{}
}

func (e *ErrorMockResponseWriter) Write(data []byte) (int, error){
    e.Body = io.NopCloser(bytes.NewReader(data)) 
    return 0, fmt.Errorf("always errors")
}

func (e *ErrorMockResponseWriter) WriteHeader(statusCode int) {
    e.Code = statusCode
}

func TestWriteResponse_NoResponseBody(testing *testing.T) {
    // TODO replace with mock returning error on .Write()
    responseWriter := ErrorMockResponseWriter()
    responseBody := make(chan int)

    WriteResponse(responseWriter, responseBody, http.StatusOK)

    assert.Equal(testing, http.StatusInternalServerError, responseWriter.Code, fmt.Sprintf("Expected status code %d, but got %d", http.StatusInternalServerError, responseWriter.Code))

    actualResponseBodyAsString := responseWriter.Body.String()

    assert.Equal(testing, "", actualResponseBodyAsString, fmt.Sprintf("Expected response body to be empty but got '%s'", actualResponseBodyAsString))
}
英文:

Just implement the interface:

type ResponseWriter interface {
    Header() Header
    Write([]byte) (int, error)
    WriteHeader(statusCode int)
}

so something like

type ErrorMockResponseWriter struct {
    Body io.ReadCloser
    Code int
}

func (e *ErrorMockResponseWriter) Header() http.Header {
    return http.Header{}
}

func (e *ErrorMockResponseWriter) Write(data []byte) (int, error){
    e.Body = io.NopCloser(bytes.NewReader(data)) 
    return 0, fmt.Errorf("always errors")
}

func (e *ErrorMockResponseWriter) WriteHeader(statusCode int) {
    e.Code = statusCode
}

func TestWriteResponse_NoResponseBody(testing *testing.T) {
    // TODO replace with mock returning error on .Write()
    responseWriter := ErrorMockResponseWriter()
    responseBody := make(chan int)

    WriteResponse(responseWriter, responseBody, http.StatusOK)

    assert.Equal(testing, http.StatusInternalServerError, responseWriter.Code, fmt.Sprintf("Expected status code %d, but got %d", http.StatusInternalServerError, responseWriter.Code))

    actualResponseBodyAsString := responseWriter.Body.String()

    assert.Equal(testing, "", actualResponseBodyAsString, fmt.Sprintf("Expected response body to be empty but got '%s'", actualResponseBodyAsString))
}

huangapple
  • 本文由 发表于 2023年8月8日 23:31:37
  • 转载请务必保留本文链接:https://go.coder-hub.com/76861059.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定