一个帮助单元测试代码的库(使用Golang的Github SDK)

72 阅读2分钟

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 包的代码编写单元测试的能力是至关重要的。

经过一些互动(几个月后),我们达成了一个可重复使用的、不过分啰嗦的测试编写方式,这就是我们的成果。