A library to aid unittesting code that uses Golang's Github SDK

Overview

go-github-mock

Go Reference Go Report Card

A library to aid unittesting code that uses Golang's Github SDK

Installation

go get github.com/migueleliasweb/go-github-mock

Features

  • Create mocks for successive calls for the same endpoint
  • Mock error returns
  • High level abstraction helps writing readabe unittests (see mock.WithRequestMatch)
  • Lower level abstraction for advanced uses (see mock.WithRequestMatchHandler)

Example

import "github.com/migueleliasweb/go-github-mock/src/mock"

Multiple requests

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"

Mocking errors from the 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"
    }
}

Why

Some conversations got started on go-github#1800 since go-github didn't provide an interface that could be easily reimplemented for unittests. After lots of conversations from the folks from go-github and quite a few PR ideas later, this style of testing was deemed not suitable to be part of the core SDK as it's not a feature of the API itself. Nonetheless, the ability of writing unittests for code that uses the go-github package is critical.

A reuseable, and not overly verbose, way of writing the tests was reached after some more interactions (months down the line) and here we are.

Thanks

Thanks for all ideas and feedback from the folks in go-github.

License

This library is distributed under the MIT License found in LICENSE.

Issues
  • Fails to handle GetArchiveLink routes

    Fails to handle GetArchiveLink routes

    Using the following logic (with a custom EndpointPattern because the already existing pattern assumes you're specifying a ref) I get an Get "": unsupported protocol scheme "" response.

    mock.WithRequestMatchHandler(mock.EndpointPattern{
      Pattern: "/repos/{owner}/{repo}/zipball",
      Method:  "GET",
    }, http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
      rw.WriteHeader(http.StatusFound)
      rw.Header().Add("Location", "https://codeload.github.com/gothinkster/react-redux-realworld-example-app/legacy.zip/refs/heads/master")
    })),
    

    I'm unsure if what I'm doing is inherently wrong, but I believe I'm returning the same response that Github would return with the http.StatusFound response and with a header identifying the location of the requested zipball.


    Also, let me just say, I really appreciate this project - it's gotten me 95% of the way where I need to be for a project I'm working on. So, thank you!

    bug 
    opened by edwardofclt 10
  • can not correctly mock GetReposContentsByOwnerByRepoByPath when path has

    can not correctly mock GetReposContentsByOwnerByRepoByPath when path has "/"

    		mock.WithRequestMatch(
    			mock.GetReposContentsByOwnerByRepoByPath,
    			github.RepositoryContent{
    				Encoding: github.String("base64"),
    				Path:     github.String("path/test-file.txt"),
    				Content:  github.String("fake-content"),
    			},
    		),
    

    if I call it with

    client.Repositories.GetContents(
    			*r.context, r.org, r.repo, github.String("path/test-file.txt"), &github.RepositoryContentGetOptions{})
    

    it will raise exception

    %!s(PANIC=Error method: runtime error: invalid memory address or nil pointer dereference)\nresponse: {{\"Response\":null,\"message\":\"mock response not found for /repos/owner/repo-name/contents/path/test-file.txt\",\"errors\":null}}"
    

    seems to be the path could not match pattern

    bug 
    opened by LeoQuote 9
  • Unable to mock Git.GetRef

    Unable to mock Git.GetRef

    I tried mocking Git.GetRef but unable to make it work

    mockedHTTPClient := mock.NewMockedHTTPClient(
    	mock.WithRequestMatch(
    		mock.GetReposGitRefByOwnerByRepoByRef,
    		github.Reference{},
    	),
    )
    c := github.NewClient(mockedHTTPClient)
    ctx := context.Background()
    
    reference, resp, err := c.Git.GetRef(ctx, "foo", "bar", "main")
    log.Println(reference)
    log.Println(resp.Response.StatusCode)
    log.Println(resp.Response.Request)
    
    // time="2022-03-23T12:21:53+04:00" level=info msg="<nil>"
    // time="2022-03-23T12:21:53+04:00" level=info msg=404
    // time="2022-03-23T12:21:53+04:00" level=info msg="&{GET http://127.0.0.1:55565/repos/foo/bar/git/ref/main HTTP/1.1 1 1 map[Accept:[application/vnd.github.v3+json] User-Agent:[go-github]] <nil> <nil> 0 [] false api.github.com map[] map[] <nil> map[]   <nil> <nil> <nil> 0xc00001c0b0}"
    

    I tried similar mock with c.Git.CreateRef and works fine. Anything I'm doing wrong that should be changed?

    bug 
    opened by oba11 8
  • Question: How to mock pagination?

    Question: How to mock pagination?

    How to mock pagination?

    Use case

    This is a slightly modified version of the pagination example taken from https://github.com/google/go-github/blob/54ec837fb63dc356426c665bddb5a5b4354e621d/README.md#pagination

    func ListAllReposByOrg(ctx context.Context, org string, client *github.Client) ([]*github.Repository, error) {
    	opt := &github.RepositoryListByOrgOptions{
    		ListOptions: github.ListOptions{PerPage: 2},
    	}
    
    	var allRepos []*github.Repository
    	for {
    		repos, resp, err := client.Repositories.ListByOrg(ctx, org, opt)
    		if err != nil {
    			return nil, err
    		}
    		allRepos = append(allRepos, repos...)
    		if resp.NextPage == 0 {
    			break
    		}
    		opt.Page = resp.NextPage
    	}
    	
    	return allRepos, nil
    }
    

    Using responsesFIFO I can mock different results for subsequent requests. But, how to mock resp.NextPage?

    mockedHTTPClient := mock.NewMockedHTTPClient(
    		mock.WithRequestMatch(
    			mock.GetOrgsReposByOrg,
    			[][]byte{
    				mock.MustMarshal([]github.Repository{
    					{
    						Name: github.String("repo-A-on-first-page"),
    					},
    					{
    						Name: github.String("repo-B-on-first-page"),
    					},
    				}),
    				mock.MustMarshal([]github.Repository{
    					{
    						Name: github.String("repo-C-on-second-page"),
    					},
    					{
    						Name: github.String("repo-D-on-second-page"),
    					},
    				}),
    			},
    		),
    	)
    

    Thanks!

    opened by TangRufus 8
  • Support for empty result

    Support for empty result

    This PR adds support for the cases when empty data should be returned (for example if no releases exists - Github returns http 200 with empty array in this case )

    opened by antonlisovenko 5
  • mock.GetRateLimits returns nothing

    mock.GetRateLimits returns nothing

    I am trying to mock the request which should return an amount of remaining requests

    mockedHTTPClient := mock.NewMockedHTTPClient(
    	mock.WithRequestMatch(
    		mock.GetRateLimit,
    		github.RateLimits{
    			Core: &github.Rate{
    				Limit:     *github.Int(1),
    				Remaining: 1,
    				Reset:     github.Timestamp{},
    			},
    			Search: &github.Rate{},
    		},
    	),
    )
    c := github.NewClient(mockedHTTPClient)
    ctx := context.Background()
    
    limits, response, err := c.RateLimits(ctx)
    
    if err != nil {
    	log.Fatal(err)
    }
    fmt.Println(response, limits, err)
    

    For some reason my output looks like this

    github.Rate{Limit:0, Remaining:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}} <nil> <nil>
    

    I was trying to return just a github.Rate, github.RateLimits. I've swapped values with github.Int. Still nothing. Use of WithRequestMatchHandler also did nothing. Am I missing something? Other requests I was trying to mock are working as expected and returns proper values.

    opened by kalgurn 3
  • Mock test for function that calls the same api request twice

    Mock test for function that calls the same api request twice

    I am having some errors when I created two MockBackendOption with PatchReposPullsByOwnerByRepoByPullNumber for the function NewMockedHTTPClient, its an EOF error , I don't know if it is an error of my code or go-GitHub-mock , if it is my error what should I do instead of that. I am trying to create a function that calls the same GitHub API request twice.

    opened by mariojose123 3
  • DeleteReposGitRefsByOwnerByRepoByRef and  /repos/test1/test1/git/refs/heads/test1 request for tests

    DeleteReposGitRefsByOwnerByRepoByRef and /repos/test1/test1/git/refs/heads/test1 request for tests

    i am trying to test a function that calls client.Git.DeleteRef method , it uses /repos/test1/test1/git/refs/heads/test1 and for tests i use DeleteReposGitRefsByOwnerByRepoByRef as mock , the problem is that its not working as intented ,the error is “mock response not found for /repos/test1/test1/git/refs/heads/test1" that client.Git.DeleteRef generetes for my test.i don't know if it is a error or it isn't.

    opened by mariojose123 1
  • Fix gen redeclarations

    Fix gen redeclarations

    • Rework gen.go to use OPENAPI definitions for Github's API ( tbh, I didn't know they were available... I looked for them a while ago and couldn't find anything)
    • Ditch using web crawling to fetch api endpoints
    • Add logger to gen.go
    opened by migueleliasweb 0
  • Fix to gen script + unittests

    Fix to gen script + unittests

    It's great to fix things and introduce even more breaking changes, hooray! :tada:

    Now, at least, the endpoints with underscores or dashes are properly handled.

    opened by migueleliasweb 0
  • Data Race on mock.FIFOResponseHandler.CurrentIndex

    Data Race on mock.FIFOResponseHandler.CurrentIndex

    Due to the fact that the http server will use a new goroutine for each request, multiple goroutines access the CurrentIndex field of the FIFOResponseHandler. When running tests with the -race flag, this can lead to 'WARNING: DATA RACE' situations.

    opened by mheck136 0
  • Dynamically create routes without optional parameters

    Dynamically create routes without optional parameters

    Some endpoints have parameters that are optional, we should be able to dynamically create endpoints without them to cater to some other code flows.

    E.g.

    "/repos/{owner}/{repo}/zipball/{ref}": {
          "get": {
            "summary": "Download a repository archive (zip)",
            "description": "Gets a redirect URL to download a zip archive for a repository. If you omit `:ref`, the repository’s default branch (usually\n`master`) will be used. Please make sure your HTTP framework is configured to follow redirects or you will need to use\nthe `Location` header to make a second `GET` request.\n**Note**: For private repositories, these links are temporary and expire after five minutes.",
            "tags": [
              "repos"
            ],
            "externalDocs": {
              "description": "API method documentation",
              "url": "https://docs.github.com/rest/reference/repos#download-a-repository-archive"
            },
            "operationId": "repos/download-zipball-archive",
            "parameters": [
              {
                "$ref": "#/components/parameters/owner"
              },
              {
                "$ref": "#/components/parameters/repo"
              },
              {
                "name": "ref",
                "in": "path",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            ],
            "responses": {
              "302": {
                "description": "Response",
                "headers": {
                  "Location": {
                    "example": "https://codeload.github.com/me/myprivate/legacy.zip/master?login=me&token=thistokenexpires",
                    "schema": {
                      "type": "string"
                    }
                  }
                }
              }
            },
            "x-github": {
              "githubCloudOnly": false,
              "enabledForGitHubApps": true,
              "category": "repos",
              "subcategory": "contents"
            }
          }
        },
    

    The above example shows that only owner and repo are required arguments.

    enhancement 
    opened by migueleliasweb 2
  • The `github.ErrorResponse.Error()` method panics

    The `github.ErrorResponse.Error()` method panics

    The ErrorResponse.Error() implementation references the *http.Response which is not provided by go-github-mock. The examples use type assertions to work around this, but wrapping errors is a common pattern in Go (e.g. fmt.Errorf("failed: %w", userErr)). If you're testing code which wraps the error, it will panic.

    diff --git a/src/mock/server_test.go b/src/mock/server_test.go
    index c88b41f..25e0d58 100644
    --- a/src/mock/server_test.go
    +++ b/src/mock/server_test.go
    @@ -118,6 +118,7 @@ func TestMockErrors(t *testing.T) {
                    t.Fatal("user err is nil, want *github.ErrorResponse")
            }
    
    +       t.Errorf("%s", userErr.Error())
            ghErr, ok := userErr.(*github.ErrorResponse)
    
            if !ok {
    
    ❯ go test ./...
    ?   	github.com/migueleliasweb/go-github-mock	[no test files]
    --- FAIL: TestMockErrors (0.00s)
    panic: runtime error: invalid memory address or nil pointer dereference [recovered]
    	panic: runtime error: invalid memory address or nil pointer dereference
    [signal SIGSEGV: segmentation violation code=0x1 addr=0x80 pc=0x12e6a1a]
    
    goroutine 50 [running]:
    testing.tRunner.func1.2(0x1334920, 0x15b67d0)
    	.../golang/1.16.3/go/src/testing/testing.go:1143 +0x332
    testing.tRunner.func1(0xc0002b2000)
    	.../golang/1.16.3/go/src/testing/testing.go:1146 +0x4b6
    panic(0x1334920, 0x15b67d0)
    	.../golang/1.16.3/go/src/runtime/panic.go:965 +0x1b9
    github.com/google/go-github/v37/github.(*ErrorResponse).Error(0xc00007c5f0, 0x0, 0x16)
    	.../go/pkg/mod/github.com/google/go-github/[email protected]/github/github.go:707 +0x3a
    github.com/migueleliasweb/go-github-mock/src/mock.TestMockErrors(0xc0002b2000)
    	.../go-github-mock/src/mock/server_test.go:121 +0x1e3
    testing.tRunner(0xc0002b2000, 0x13b3ef8)
    	.../golang/1.16.3/go/src/testing/testing.go:1193 +0xef
    created by testing.(*T).Run
    	.../golang/1.16.3/go/src/testing/testing.go:1238 +0x2b3
    FAIL	github.com/migueleliasweb/go-github-mock/src/mock	0.133s
    FAIL
    
    bug help wanted 
    opened by sebnow 3
Releases(v0.0.9-beta)
  • v0.0.9-beta(Apr 16, 2022)

    What's Changed

    • Allow extended last parameters by @migueleliasweb in https://github.com/migueleliasweb/go-github-mock/pull/30

    Fixes

    • https://github.com/migueleliasweb/go-github-mock/issues/24
    • https://github.com/migueleliasweb/go-github-mock/issues/26
    Source code(tar.gz)
    Source code(zip)
  • v0.0.8(Mar 23, 2022)

    What's Changed

    • Update github definitions by @migueleliasweb in https://github.com/migueleliasweb/go-github-mock/pull/25

    Full Changelog: https://github.com/migueleliasweb/go-github-mock/compare/v0.0.7...v0.0.8

    Source code(tar.gz)
    Source code(zip)
  • v0.0.7(Feb 20, 2022)

    What's Changed

    • Update GH OpenApi definitions by @migueleliasweb in https://github.com/migueleliasweb/go-github-mock/pull/23

    Full Changelog: https://github.com/migueleliasweb/go-github-mock/compare/v0.0.6...v0.0.7

    Source code(tar.gz)
    Source code(zip)
Owner
Miguel Elias dos Santos
Miguel Elias dos Santos
Test your code without writing mocks with ephemeral Docker containers 📦 Setup popular services with just a couple lines of code ⏱️ No bash, no yaml, only code 💻

Gnomock – tests without mocks ??️ Spin up entire dependency stack ?? Setup initial dependency state – easily! ?? Test against actual, close to product

Yury Fedorov 845 Jun 26, 2022
Just Dance Unlimited mock-up server written on Golang and uses a popular Gin framework for Go.

BDCS Just Dance Unlimited mock-up server written on Golang and uses a popular Gin framework for Go. Features Security Authorization works using UbiSer

Mikhail 0 Nov 10, 2021
Terratest is a Go library that makes it easier to write automated tests for your infrastructure code.

Terratest is a Go library that makes it easier to write automated tests for your infrastructure code. It provides a variety of helper functions and patterns for common infrastructure testing tasks,

Gruntwork 6.1k Jun 22, 2022
:octocat: ghdag is a tiny workflow engine for GitHub issue and pull request.

ghdag ghdag is a tiny workflow engine for GitHub issue and pull request. Key features of ghdag are: Simple definition of workflows to improve the life

Ken’ichiro Oyama 20 May 29, 2022
go-wrk - a HTTP benchmarking tool based in spirit on the excellent wrk tool (https://github.com/wg/wrk)

go-wrk - an HTTP benchmarking tool go-wrk is a modern HTTP benchmarking tool capable of generating significant load when run on a single multi-core CP

Tal Sliwowicz 560 Jun 26, 2022
Example usage of github.com/go-webauthn/webauthn

Example This is an example go + React application which shows off the functionality of the github.com/go-webauthn/webauthn library. Features This proj

null 2 Mar 18, 2022
Mutation testing for Go source code

go-mutesting go-mutesting is a framework for performing mutation testing on Go source code. Its main purpose is to find source code, which is not cove

Markus Zimmermann 536 Jun 22, 2022
mockery - A mock code autogenerator for Golang

mockery - A mock code autogenerator for Golang

Vektra 3.6k Jun 26, 2022
A Master list of Go Programming Tutorials, their write-ups, their source code and their current build status!

TutorialEdge TutorialEdge.net Go Tutorials ??‍?? ??‍?? Welcome to the TutorialEdge Go Repository! The goal of this repo is to be able to keep track of

TutorialEdge 274 Jun 15, 2022
Automatically generate Go test boilerplate from your source code.

gotests gotests makes writing Go tests easy. It's a Golang commandline tool that generates table driven tests based on its target source files' functi

Charles Weill 4k Jul 1, 2022
Demo repository for Infrastructure as Code testing tools and frameworks.

Testing Infrastructure as Code Demo repository for Infrastructure as Code testing tools and frameworks. Maintainer M.-Leander Reimer (@lreimer), mario

M.-Leander Reimer 4 Jan 23, 2022
Sample code for a quick demo of go 1.18's fuzzing

Fuzzing in Go 1.18 What is it? "Fuzzing is a type of automated testing which continuously manipulates inputs to a program to find bugs. Go fuzzing use

David Lanouette 0 Feb 11, 2022
Fortio load testing library, command line tool, advanced echo server and web UI in go (golang). Allows to specify a set query-per-second load and record latency histograms and other useful stats.

Fortio Fortio (Φορτίο) started as, and is, Istio's load testing tool and now graduated to be its own project. Fortio is also used by, among others, Me

Fortio (Φορτίο) 2.6k Jun 24, 2022
:exclamation:Basic Assertion Library used along side native go testing, with building blocks for custom assertions

Package assert Package assert is a Basic Assertion library used along side native go testing Installation Use go get. go get github.com/go-playground/

Go Playgound 39 Apr 17, 2022
Library created for testing JSON against patterns.

Gomatch Library created for testing JSON against patterns. The goal was to be able to validate JSON focusing only on parts essential in given test cas

null 41 Dec 1, 2021
A BDD library for Go

gospecify This provides a BDD syntax for testing your Go code. It should be familiar to anybody who has used libraries such as rspec. Installation The

Samuel Tesla 52 Nov 22, 2021
A Go test assertion library for verifying that two representations of JSON are semantically equal

jsonassert is a Go test assertion library for verifying that two representations of JSON are semantically equal. Usage Create a new *jsonassert.Assert

Roger Guldbrandsen 85 Jun 14, 2022
A Go library help testing your RESTful API application

RESTit A Go micro-framework to help writing RESTful API integration test Package RESTit provides helps to those who want to write an integration test

RESTit 55 Nov 25, 2021
testcase is an opinionated behavior-driven-testing library

Table of Contents testcase Guide Official API Documentation Getting Started / Example Modules Summary DRY Modularization Stability Case Study About te

Adam Luzsi 95 Jun 20, 2022