如何在 Go 中进行模拟

问题描述 投票:0回答:1

我正在用 Golang 编写一个注册路由测试。我有点被模拟困住了。来自 Rails 和 Rspec,您执行了以下操作

allow_any_instance_of(EmailDelivery).to receive(:send) 

并且电子邮件未发送

在 go 中,我使用 EmailDelivery 结构和方法

Send
我想在测试中模拟这个方法,不做任何事情

这里需要使用接口吗?还有其他“神奇”的方式吗,或者它在 Go 中就不是惯用的了

目前,我有 struct

Router
,我在其中注册路由 - 像这样(使用 mux 路由器)

router.HandleFunc('/register', WithCors(router.RegisterHandler)).Methods(method, "OPTIONS")

看来我应该使用方法

EmailDelivery
和结构 RealEmailDelivery 创建一个接口
Send
。然后在测试中,我应该创建新的结构 FakeEmailDelivery 并实现 Send 方法而不执行任何操作。这样可以吗?

从那里开始,我看到两个选择:

  1. RegisterHandler 接受带有参数的接口,然后在测试中执行请求时,我这样做
    router.RegisterHandler(w, req, fakeEmailDelivery)
  1. 更合适的选择似乎是路由器构造函数(NewRouter)接受 EmailDelivery 接口并为所有路由使用相同的电子邮件传递。无论如何,这是有道理的。然而,我在这里将路由器和发送电子邮件结合起来,这是两个完全不同的事情。为什么路由器应该知道有关发送电子邮件的信息?

您对这两种选择有何看法?您将如何以最佳方式组织这段代码?

提前致谢

go mocking go-testing
1个回答
0
投票

基本上像往常一样:针对接口进行开发,编写这些接口的模拟实现并在单元测试中使用它。

概念解释

为了保持您的示例,让我们看一下邮件程序。

type Mailer interface {
   Send(to []string, subject, body string) bool
}

现在,您的处理程序使用此邮件程序:

type FooHandler struct {
   Mailer mailer
}

func(h *FooHandler) ServeHTTP(w http.ResponseWriter, r *http.Request){
   mailer.Send([]string{"[email protected]"},"Test", "Just a test.")
}

在单元测试中,您现在可以实现 Mock 或 Stub 或使用 https://github.com/stretchr/testify 的 mock 包:

type MockMailer struct {
  mock.Mock
}

func (m *MockMailer) Send(to, cc, bcc []string, subject, body string) {
  // Setup your mock here
}

func TestHandler(t *testing.T){
  h := FooHandler(Mailer: new(MockMailer))
  // Do what you must
}

工作示例

package main

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

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

const (
    recipient = "[email protected]"
)

// Mailer is a heavily simplified interface for sending emails.
// In a real world scenario, you would probably have a LOT more parameters.
type Mailer interface {
    Send(to []string, subject, body string) bool
}

// FooHandler is a simple HTTP handler that sends an email and
// stands in for a more complex handler.
type FooHandler struct {
    mailer Mailer
}

// Message is a simple struct that holds the email subject and body.
// Mainly used for illustration purposes.
type Message struct {
    Subject string
    Body    string
}

// ServeHTTP implements the http.Handler interface.
// It decodes the request body into a Message struct and sends it via the Mailer.
// In a real world scenario, you would probably have a lot more error handling.
func (h *FooHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    var msg Message
    if err := json.NewDecoder(r.Body).Decode(&msg); err != nil {
        http.Error(w, "Bad Request: "+err.Error(), http.StatusBadRequest)
        return
    }
    if sent := h.mailer.Send([]string{recipient}, msg.Subject, msg.Body); !sent {
        http.Error(w, "Internal Server Error", http.StatusInternalServerError)
        return
    }
}

// MockMailer is a mock implementation of the Mailer interface.
// Here is where the "magic" happens. We use the testify/mock package
// to create a mock implementation of the Mailer interface.
// The mock implementation is then used in our tests to assert
// that the correct methods are called with the correct parameters.
type MockMailer struct {
    mock.Mock
}

// Send is the mock implementation of the Mailer interface.
// It asserts that the correct parameters are passed to the method
// and returns a predefined value.
func (m *MockMailer) Send(to []string, subject, body string) bool {
    args := m.Called(to, subject, body)
    return args.Bool(0)
}

func TestHandler(t *testing.T) {
    mailer := new(MockMailer)
    ok := mailer.On("Send", []string{recipient}, "Test", "Just a test.").Return(true)
    fail := mailer.On("Send", []string{recipient}, "Fail", "Will fail").Return(false)
    testCases := []struct {
        desc           string
        mock           *mock.Call
        message        Message
        expectedStatus int
    }{
        {
            desc:           "Successfull mailing",
            mock:           ok,
            message:        Message{Subject: "Test", Body: "Just a test."},
            expectedStatus: http.StatusOK,
        },
        {
            desc:           "Failed mailing",
            mock:           fail,
            message:        Message{Subject: "Fail", Body: "Will fail"},
            expectedStatus: http.StatusInternalServerError,
        },
    }
    for _, tC := range testCases {
        t.Run(tC.desc, func(t *testing.T) {
            json, err := json.Marshal(tC.message)
            assert.NoError(t, err)

            handler := &FooHandler{mailer: mailer}
            
            recorder := httptest.NewRecorder()
            handler.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", bytes.NewReader(json)))
            assert.Equal(t, tC.expectedStatus, recorder.Code)
        })
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.