go-github-mock
一个帮助单元测试代码的库,使用Golang的Github SDK。
安装
go get github.com/migueleliasweb/go-github-mock
特点
- 为同一个端点的连续调用创建Mock
- 模拟错误返回
- 高层次的抽象有助于编写可读的单元测试(见
mock.WithRequestMatch)。 - 较低级别的抽象用于高级用途(见
mock.WithRequestMatchHandler)
例子
import "github.com/migueleliasweb/go-github-mock/src/mock"
多个请求
mockedHTTPClient := mock.NewMockedHTTPClient(
mock.WithRequestMatch(
mock.GetUsersByUsername,
[][]byte{
mock.MustMarshal(github.User{
Name: github.String("foobar"),
}),
},
),
mock.WithRequestMatch(
mock.GetUsersOrgsByUsername,
[][]byte{
mock.MustMarshal([]github.Organization{
{
Name: github.String("foobar123thisorgwasmocked"),
},
}),
},
),
mock.WithRequestMatchHandler(
mock.GetOrgsProjectsByOrg,
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.Write(mock.MustMarshal([]github.Project{
{
Name: github.String("mocked-proj-1"),
},
{
Name: github.String("mocked-proj-2"),
},
}))
}),
),
)
c := github.NewClient(mockedHTTPClient)
ctx := context.Background()
user, _, userErr := c.Users.Get(ctx, "myuser")
// user.Name == "foobar"
orgs, _, orgsErr := c.Organizations.List(
ctx,
*(user.Name),
nil,
)
// orgs[0].Name == "foobar123thisorgwasmocked"
projs, _, projsErr := c.Organizations.ListProjects(
ctx,
*orgs[0].Name,
&github.ProjectListOptions{},
)
// projs[0].Name == "mocked-proj-1"
// projs[1].Name == "mocked-proj-2"
嘲弄来自API的错误
mockedHTTPClient := mock.NewMockedHTTPClient(
mock.WithRequestMatchHandler(
mock.GetUsersByUsername,
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
WriteError(
w,
http.StatusInternalServerError,
"github went belly up or something",
)
}),
),
)
c := github.NewClient(mockedHTTPClient)
ctx := context.Background()
user, _, userErr := c.Users.Get(ctx, "someUser")
// user == nil
if userErr == nil {
if ghErr, ok := userErr.(*github.ErrorResponse); ok {
fmt.Println(ghErr.Message) // == "github went belly up or something"
}
}
用分页法嘲弄
mockedHTTPClient := NewMockedHTTPClient(
WithRequestMatchPages(
GetOrgsReposByOrg,
[][]byte{
MustMarshal([]github.Repository{
{
Name: github.String("repo-A-on-first-page"),
},
{
Name: github.String("repo-B-on-first-page"),
},
}),
MustMarshal([]github.Repository{
{
Name: github.String("repo-C-on-second-page"),
},
{
Name: github.String("repo-D-on-second-page"),
},
}),
},
),
)
c := github.NewClient(mockedHTTPClient)
ctx := context.Background()
opt := &github.RepositoryListByOrgOptions{
ListOptions: github.ListOptions{
// in fact, the perPage option is ignored my the mocks
// but this would be present in production code
PerPage: 2,
},
}
var allRepos []*github.Repository
for {
repos, resp, listErr := c.Repositories.ListByOrg(ctx, "foobar", opt)
if listErr != nil {
t.Errorf("error listing repositories: %s", listErr.Error())
}
// len(repos) == 2
allRepos = append(allRepos, repos...)
if resp.NextPage == 0 {
break
}
opt.Page = resp.NextPage
}
// matches the mock definitions (len(page[0]) + len(page[1])
// len(allRepos) == 4
为什么
在go-github#1800上开始了一些对话,因为go-github ,没有提供一个可以很容易地重新实现单元测试的接口。经过go-github的朋友们的大量对话和相当多的PR想法后,这种测试方式被认为不适合作为核心SDK的一部分,因为它不是API本身的一个功能。尽管如此,为使用go-github 包的代码编写单元测试的能力是至关重要的。
经过一些互动(几个月后),我们达成了一个可重复使用的、不过分啰嗦的测试编写方式,这就是我们的成果。