Go中的条件模拟

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

Conditional Mocking in Go

问题

我想根据特定条件在GoLang中模拟Rest API调用。
例如,我有一个如下所示的方法:

func Method() {
  resp1, err := DoSomething1()
  resp2, err := DoSomething2()
}

func DoSomething1() (*http.Response, error) {
  return Get(url string)
}

func DoSomething2() (*http.Response, error) {
  return Get(url string)
}

现在,我想为每个get调用单独定义模拟响应。也就是说,当我从Method()调用DoSomething1()时,我想模拟get调用并返回响应R1,当我从Method()调用DoSomething2()时,我想模拟get调用并返回响应R2。

到目前为止,我已经能够模拟单个get调用,该调用将为任何请求返回相同的响应,方法是将Get定义为接口并进行模拟。但是,我正在尝试为这些调用定义响应。

下面是我定义的用于模拟get/post调用的模拟接口:

package clientMock

import (
	"net/http"
)

type MockClient struct {
}

var (
	GetMock  func(url string, queryParams map[string]interface{}, headerParams map[string]interface{}) (*http.Response, error)
	PostMock func(url string, reqBody interface{}, queryParams map[string]interface{}, headerParams map[string]interface{}) (*http.Response, error)
)

func (m MockClient) Get(url string, queryParams map[string]interface{}, headerParams map[string]interface{}) (*http.Response, error) {
	return GetMock(url, queryParams, headerParams)
}

func (m MockClient) Post(url string, reqBody interface{}, queryParams map[string]interface{}, headerParams map[string]interface{}) (*http.Response, error) {
	return PostMock(url, reqBody, queryParams, headerParams)
}

下面是我为测试Method定义的示例测试类,但是我无法弄清楚如何在Method()中调用DoSomething2()时更改模拟响应:

func TestHandleSuccess(t *testing.T) {

	resBody := `{"instance_url":"instance_url","access_token":"access_token"}`
	r := ioutil.NopCloser(bytes.NewReader([]byte(resBody)))
	clientMock.GetMock = func(url string, queryParams, headerParams map[string]interface{}) (*http.Response, error) {
		return &http.Response{
			StatusCode: 200,
			Body:       r,
		}, nil

	}

	err := method.Method()
	assert.Nil(t, err)
}
英文:

I want to mock Rest API calls in GoLang based on certain condition.
For example, i have a method defined like below :

func Method() {
  resp1, err := DoSomething1()
  resp2, err := DoSomething2()
}

func DoSomething1() (*http.Response, error) {
  return Get(url string)
}

func DoSomething2() (*http.Response, error) {
  return Get(url string)
}

Now, i want to define my mock response for each get calls separately. i.e when i call DoSomething1() from Method(), i want to mock the get call and return response R1 and when i call DoSomething2() from Method(), i want to mock the get call and return response R2.

So far, i have been able to mock a single get call that would return same response for any request by defining Get as an interface and mocking it, however i am trying to define responses for each of these calls.

Below is the mock interface that i have defined that i am using to mock get/post calls :

package clientMock

import (
	"net/http"
)

type MockClient struct {
}

var (
	GetMock  func(url string, queryParams map[string]interface{}, headerParams map[string]interface{}) (*http.Response, error)
	PostMock func(url string, reqBody interface{}, queryParams map[string]interface{}, headerParams map[string]interface{}) (*http.Response, error)
)

func (m MockClient) Get(url string, queryParams map[string]interface{}, headerParams map[string]interface{}) (*http.Response, error) {
	return GetMock(url, queryParams, headerParams)
}

func (m MockClient) Post(url string, reqBody interface{}, queryParams map[string]interface{}, headerParams map[string]interface{}) (*http.Response, error) {
	return PostMock(url, reqBody, queryParams, headerParams)
}

Below is the sample Test Class that i have defined for testing Method, however unable to figure out how can i change the mock response when DoSomething2() is being called from Method() :

func TestHandleSuccess(t *testing.T) {

	resBody := `{"instance_url":"instance_url","access_token":"access_token"}`
	r := ioutil.NopCloser(bytes.NewReader([]byte(resBody)))
	clientMock.GetMock = func(url string, queryParams, headerParams map[string]interface{}) (*http.Response, error) {
		return &http.Response{
			StatusCode: 200,
			Body:       r,
		}, nil

	}

	err := method.Method()
	assert.Nil(t, err)
}

答案1

得分: 0

根据你提供的代码,我猜测从你的“做某事”函数传递给Get的参数url是不同的(否则我不确定为什么你需要改变行为)。如果是这样的话,你可以在模拟函数中添加一个条件,根据url来改变行为。

以下是代码示例:

clientMock.GetMock = func(url string, queryParams, headerParams map[string]interface{}) (*http.Response, error) {
	var resBody string
	if url == "/call1" {
		resBody = `{"instance_url":"instance_url","access_token":"access_token"}`
	} else if url == "/call2" {
		resBody = `{"something":"else"}`
	}
	return &http.Response{
		StatusCode: 200,
		Body:       ioutil.NopCloser(bytes.NewReader([]byte(resBody))),
	}, nil
}

你应该这样思考你的代码 - 行为不应该根据调用的位置而改变,而应该根据调用函数的输入来改变。这样可以将函数与其调用者解耦,使重用和重构更容易。

英文:

Given the code you've provided, I'm guessing the parameter url passed into Get from your "do something" functions is different (otherwise I'm not sure why you'd need the behavior to change). If this is the case, you could add a condition in your mock to change behavior based on the url.

clientMock.GetMock = func(url string, queryParams, headerParams map[string]interface{}) (*http.Response, error) {
	var resBody string
	if url == "/call1" {
		resBody = `{"instance_url":"instance_url","access_token":"access_token"}`
	} else if url == "/call2" {
		resBody = `{"something":"else"}`
	}
	return &http.Response{
		StatusCode: 200,
		Body:       ioutil.NopCloser(bytes.NewReader([]byte(resBody))),
	}, nil
}

This is how you should think about your code - behavior shouldn't change based on where a call is coming from. Instead, it should change based on the inputs to the function being called. This decouples your function from its caller and makes reuse and refactoring easier.

huangapple
  • 本文由 发表于 2021年5月25日 21:02:34
  • 转载请务必保留本文链接:https://go.coder-hub.com/67688526.html
匿名

发表评论

匿名网友

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

确定