From 80740d0b95fb724931b042042801e1d696da146c Mon Sep 17 00:00:00 2001 From: Yann Date: Wed, 5 Jul 2023 12:28:27 +0200 Subject: [PATCH] ci: add ci with tests --- .github/workflows/ci.yml | 54 ++++++++++++++++++++++++++ .golangci.yml | 33 ++++++++++++++++ api.go | 59 ++++++++++++++--------------- api_test.go | 11 +++--- invocations.go | 5 ++- invocations_test.go | 10 ++--- stubs.go | 12 +++--- stubs_test.go | 30 +++++++-------- verify.go | 4 +- verify_test.go | 82 ++++++++++++++++++++-------------------- 10 files changed, 192 insertions(+), 108 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .golangci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..de02ae4 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,54 @@ +name: Build + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + workflow_dispatch: + +jobs: + test: + strategy: + matrix: + go-version: [1.18.x, 1.19.x, tip] + lint-and-coverage: [false] + include: + - go-version: 1.20.x + lint-and-coverage: true + + runs-on: ubuntu-latest + + steps: + - name: Setup go + run: | + curl -sL https://raw.githubusercontent.com/maxatome/install-go/v3.4/install-go.pl | + perl - ${{ matrix.go-version }} $HOME/go + + - name: Checkout code + uses: actions/checkout@v2 + + - name: Linting + if: matrix.lint-and-coverage + run: | + curl -sL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | + sh -s -- -b $HOME/go/bin v1.52.2 + $HOME/go/bin/golangci-lint run ./... + + - name: Testing + continue-on-error: ${{ matrix.go-version == 'tip' }} + run: | + go version + if [ ${{ matrix.lint-and-coverage }} = true ]; then + GO_TEST_OPTS="-covermode=atomic -coverprofile=coverage.out" + fi + export GORACE="halt_on_error=1" + go test -race $GO_TEST_OPTS ./... + + - name: Reporting coverage + if: matrix.lint-and-coverage + env: + COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + go install github.com/mattn/goveralls@v0.0.11 + goveralls -coverprofile=coverage.out -service=github diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..2ddd0ab --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,33 @@ +run: + # See the dedicated "run" documentation section. + +output: + format: colored-line-number + + +linters-settings: + # See the dedicated "linters-settings" documentation section. + +linters: + enable: + - asasalint + - asciicheck + - bidichk + - durationcheck + - exportloopref + - gocritic + - godot + - goimports + - govet + - misspell + - prealloc + - revive + - unconvert + - whitespace + +issues: + max-issues-per-linter: 0 + max-same-issues: 0 + +severity: + # See the dedicated "severity" documentation section. diff --git a/api.go b/api.go index 45cf56f..439736e 100644 --- a/api.go +++ b/api.go @@ -7,7 +7,7 @@ import ( "strings" ) -// TestingT is an interface wrapper around *testing.T +// TestingT is an interface wrapper around *testing.T. type TestingT interface { Error(args ...any) Errorf(format string, args ...any) @@ -15,37 +15,36 @@ type TestingT interface { Fatalf(format string, args ...any) } -type ApiMock struct { +type APIMock struct { testServer *httptest.Server - calls map[HttpCall]http.HandlerFunc + calls map[HTTPCall]http.HandlerFunc testState TestingT - invocations map[HttpCall][]*Invocation + invocations map[HTTPCall][]*Invocation } -type HttpCall struct { +type HTTPCall struct { Method string Path string } -func Api(testState TestingT) *ApiMock { - mockedApi := &ApiMock{ - calls: map[HttpCall]http.HandlerFunc{}, +func API(testState TestingT) *APIMock { + mockedAPI := &APIMock{ + calls: map[HTTPCall]http.HandlerFunc{}, testState: testState, - invocations: map[HttpCall][]*Invocation{}, + invocations: map[HTTPCall][]*Invocation{}, } testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, request *http.Request) { - - call := HttpCall{ + call := HTTPCall{ Method: strings.ToLower(request.Method), Path: request.RequestURI, } - invocations := mockedApi.invocations[call] + invocations := mockedAPI.invocations[call] invocations = append(invocations, newInvocation(request, testState)) - mockedApi.invocations[call] = invocations + mockedAPI.invocations[call] = invocations - handler := mockedApi.calls[call] + handler := mockedAPI.calls[call] if handler != nil { handler(res, request) } else { @@ -53,41 +52,41 @@ func Api(testState TestingT) *ApiMock { testState.Fatalf("unmocked invocation %s %s\n", call.Method, call.Path) } })) - mockedApi.testServer = testServer + mockedAPI.testServer = testServer - return mockedApi + return mockedAPI } -func (mockedApi *ApiMock) Close() { - mockedApi.testServer.Close() +func (mockedAPI *APIMock) Close() { + mockedAPI.testServer.Close() } -func (mockedApi *ApiMock) GetUrl() *url.URL { - testServerUrl, err := url.Parse(mockedApi.testServer.URL) +func (mockedAPI *APIMock) GetURL() *url.URL { + testServerURL, err := url.Parse(mockedAPI.testServer.URL) if err != nil { - mockedApi.testState.Fatal(err) + mockedAPI.testState.Fatal(err) } - return testServerUrl + return testServerURL } -func (mockedApi *ApiMock) GetHost() string { - return mockedApi.GetUrl().Host +func (mockedAPI *APIMock) GetHost() string { + return mockedAPI.GetURL().Host } -func (mockedApi *ApiMock) Stub(method string, path string) *StubBuilder { +func (mockedAPI *APIMock) Stub(method string, path string) *StubBuilder { return &StubBuilder{ - api: mockedApi, - call: &HttpCall{ + api: mockedAPI, + call: &HTTPCall{ Method: strings.ToLower(method), Path: path, }, } } -func (mockedApi *ApiMock) Verify(method string, path string) *CallVerifier { +func (mockedAPI *APIMock) Verify(method string, path string) *CallVerifier { return &CallVerifier{ - api: mockedApi, - call: &HttpCall{ + api: mockedAPI, + call: &HTTPCall{ Method: strings.ToLower(method), Path: path, }, diff --git a/api_test.go b/api_test.go index 4634c15..1e9b739 100644 --- a/api_test.go +++ b/api_test.go @@ -1,8 +1,9 @@ package mockhttp import ( - assertions "github.com/stretchr/testify/assert" "testing" + + assertions "github.com/stretchr/testify/assert" ) type MockT struct { @@ -62,11 +63,11 @@ func (testState *MockT) assertFailedWithFatal() { func TestApiUrl(t *testing.T) { // Arrange - mockedApi := Api(NewTestingMock(t)) - defer func() { mockedApi.Close() }() + mockedAPI := API(NewTestingMock(t)) + defer func() { mockedAPI.Close() }() // Assert assert := assertions.New(t) - assert.Equal(mockedApi.testServer.URL, mockedApi.GetUrl().String()) - assert.Equal(mockedApi.GetUrl().Host, mockedApi.GetHost()) + assert.Equal(mockedAPI.testServer.URL, mockedAPI.GetURL().String()) + assert.Equal(mockedAPI.GetURL().Host, mockedAPI.GetHost()) } diff --git a/invocations.go b/invocations.go index d0a2a30..6f0fe2b 100644 --- a/invocations.go +++ b/invocations.go @@ -3,9 +3,10 @@ package mockhttp import ( "bytes" "encoding/json" - assertions "github.com/stretchr/testify/assert" "io" "net/http" + + assertions "github.com/stretchr/testify/assert" ) type Invocation struct { @@ -64,7 +65,7 @@ func (call *Invocation) WithStringPayload(expected string) *Invocation { return call } -func (call *Invocation) ReadJsonPayload(obj any) { +func (call *Invocation) ReadJSONPayload(obj any) { err := json.Unmarshal(call.GetPayload(), obj) if err != nil { call.testState.Fatal(err) diff --git a/invocations_test.go b/invocations_test.go index 3b3106d..b7864f0 100644 --- a/invocations_test.go +++ b/invocations_test.go @@ -2,9 +2,10 @@ package mockhttp import ( "bytes" - assertions "github.com/stretchr/testify/assert" "net/http" "testing" + + assertions "github.com/stretchr/testify/assert" ) func TestInvocation_GetRequest(t *testing.T) { @@ -126,7 +127,6 @@ func TestInvocation_WithPayload_Pass(t *testing.T) { invocation.WithPayload([]byte("foo")) testState.assertDidNotFailed() - } func TestInvocation_WithPayload_Fail(t *testing.T) { @@ -176,7 +176,7 @@ func TestInvocation_ReadJsonPayload(t *testing.T) { Foo string `json:"foo"` }{} - invocation.ReadJsonPayload(&json) + invocation.ReadJSONPayload(&json) assert := assertions.New(t) assert.Equal("bar", json.Foo) @@ -190,7 +190,7 @@ func TestInvocation_ReadJsonPayload_ErrorHandling(t *testing.T) { json := struct { Foo string `json:"foo"` }{} - invocation.ReadJsonPayload(&json) + invocation.ReadJSONPayload(&json) testState.assertFailedWithFatal() } @@ -201,7 +201,6 @@ func buildRequest(t *testing.T, method string, url string, data []byte) *http.Re t.Fatal(err) } return request - } func buildRequestWithBody(t *testing.T, data []byte) *http.Request { @@ -210,5 +209,4 @@ func buildRequestWithBody(t *testing.T, data []byte) *http.Request { t.Fatal(err) } return request - } diff --git a/stubs.go b/stubs.go index c6890a7..b3bf3da 100644 --- a/stubs.go +++ b/stubs.go @@ -7,24 +7,23 @@ import ( ) type StubBuilder struct { - api *ApiMock - call *HttpCall + api *APIMock + call *HTTPCall } -func (stub *StubBuilder) With(handler http.HandlerFunc) *ApiMock { +func (stub *StubBuilder) With(handler http.HandlerFunc) *APIMock { stub.api.calls[*stub.call] = handler return stub.api } -func (stub *StubBuilder) WithStatusCode(statusCode int) *ApiMock { +func (stub *StubBuilder) WithStatusCode(statusCode int) *APIMock { return stub.With(func(writer http.ResponseWriter, request *http.Request) { writer.WriteHeader(statusCode) }) } -func (stub *StubBuilder) WithJson(statusCode int, content interface{}) *ApiMock { +func (stub *StubBuilder) WithJSON(statusCode int, content interface{}) *APIMock { return stub.With(func(writer http.ResponseWriter, request *http.Request) { - writer.Header().Add("Content-Type", "application/json") writer.WriteHeader(statusCode) @@ -36,6 +35,5 @@ func (stub *StubBuilder) WithJson(statusCode int, content interface{}) *ApiMock if err != nil { log.Fatal(err) } - }) } diff --git a/stubs_test.go b/stubs_test.go index e7bb02e..a3f3df6 100644 --- a/stubs_test.go +++ b/stubs_test.go @@ -1,21 +1,22 @@ package mockhttp import ( - "github.com/gavv/httpexpect/v2" - assertions "github.com/stretchr/testify/assert" "net/http" "testing" + + "github.com/gavv/httpexpect/v2" + assertions "github.com/stretchr/testify/assert" ) func TestApiNotStubbedEndpoint(t *testing.T) { // Arrange testState := NewTestingMock(t) - mockedApi := Api(testState) - defer func() { mockedApi.Close() }() + mockedAPI := API(testState) + defer func() { mockedAPI.Close() }() // Act client := http.Client{} - response, err := client.Get(mockedApi.GetUrl().String() + "/endpoint") + response, err := client.Get(mockedAPI.GetURL().String() + "/endpoint") // Assert assert := assertions.New(t) @@ -27,13 +28,12 @@ func TestApiNotStubbedEndpoint(t *testing.T) { func TestApiStubbedEndpoint(t *testing.T) { // Arrange testState := NewTestingMock(t) - mockedApi := Api(testState) - defer func() { mockedApi.Close() }() + mockedAPI := API(testState) + defer func() { mockedAPI.Close() }() - mockedApi. + mockedAPI. Stub(http.MethodGet, "/endpoint"). With(func(writer http.ResponseWriter, request *http.Request) { - writer.Header().Add("Content-Type", "text/plain") writer.WriteHeader(201) _, err := writer.Write([]byte("Hello")) @@ -43,7 +43,7 @@ func TestApiStubbedEndpoint(t *testing.T) { }) // Act - e := httpexpect.Default(t, mockedApi.GetUrl().String()) + e := httpexpect.Default(t, mockedAPI.GetURL().String()) // Assert e.GET("/endpoint"). @@ -57,17 +57,17 @@ func TestApiStubbedEndpoint(t *testing.T) { func TestApiStubbedEndpointWithJson(t *testing.T) { // Arrange testState := NewTestingMock(t) - mockedApi := Api(testState) - defer func() { mockedApi.Close() }() + mockedAPI := API(testState) + defer func() { mockedAPI.Close() }() - mockedApi. + mockedAPI. Stub(http.MethodGet, "/endpoint"). - WithJson(http.StatusOK, struct { + WithJSON(http.StatusOK, struct { Value string `json:"value"` }{Value: "Hello"}) // Act - e := httpexpect.Default(t, mockedApi.GetUrl().String()) + e := httpexpect.Default(t, mockedAPI.GetURL().String()) // Assert testState.assertDidNotFailed() diff --git a/verify.go b/verify.go index 4c77238..5e49c32 100644 --- a/verify.go +++ b/verify.go @@ -1,8 +1,8 @@ package mockhttp type CallVerifier struct { - api *ApiMock - call *HttpCall + api *APIMock + call *HTTPCall } func (verifier *CallVerifier) HasBeenCalled(expectedCallsCount int) []*Invocation { diff --git a/verify_test.go b/verify_test.go index f927604..f08ca49 100644 --- a/verify_test.go +++ b/verify_test.go @@ -2,28 +2,29 @@ package mockhttp import ( "bytes" - assertions "github.com/stretchr/testify/assert" "net/http" "testing" + + assertions "github.com/stretchr/testify/assert" ) func TestVerifyingInvocationsCountPasses(t *testing.T) { // Arrange testState := NewTestingMock(t) - mockedApi := Api(testState) - defer func() { mockedApi.Close() }() + mockedAPI := API(testState) + defer func() { mockedAPI.Close() }() - mockedApi. + mockedAPI. Stub(http.MethodGet, "/endpoint"). - WithJson(http.StatusOK, struct { + WithJSON(http.StatusOK, struct { Value string `json:"value"` }{Value: "Hello"}) // Act client := http.Client{} - _, _ = client.Get(mockedApi.GetUrl().String() + "/endpoint") - _, _ = client.Get(mockedApi.GetUrl().String() + "/endpoint") - mockedApi.Verify(http.MethodGet, "/endpoint").HasBeenCalled(2) + _, _ = client.Get(mockedAPI.GetURL().String() + "/endpoint") + _, _ = client.Get(mockedAPI.GetURL().String() + "/endpoint") + mockedAPI.Verify(http.MethodGet, "/endpoint").HasBeenCalled(2) // Assert testState.assertDidNotFailed() @@ -32,20 +33,20 @@ func TestVerifyingInvocationsCountPasses(t *testing.T) { func TestVerifyingInvocationsCountFails(t *testing.T) { // Arrange testState := NewTestingMock(t) - mockedApi := Api(testState) - defer func() { mockedApi.Close() }() + mockedAPI := API(testState) + defer func() { mockedAPI.Close() }() - mockedApi. + mockedAPI. Stub(http.MethodGet, "/endpoint"). - WithJson(http.StatusOK, struct { + WithJSON(http.StatusOK, struct { Value string `json:"value"` }{Value: "Hello"}) // Act client := http.Client{} - _, _ = client.Get(mockedApi.GetUrl().String() + "/endpoint") - _, _ = client.Get(mockedApi.GetUrl().String() + "/endpoint") - mockedApi.Verify(http.MethodGet, "/endpoint").HasBeenCalled(3) + _, _ = client.Get(mockedAPI.GetURL().String() + "/endpoint") + _, _ = client.Get(mockedAPI.GetURL().String() + "/endpoint") + mockedAPI.Verify(http.MethodGet, "/endpoint").HasBeenCalled(3) // Assert testState.assertFailedWithFatal() @@ -54,20 +55,20 @@ func TestVerifyingInvocationsCountFails(t *testing.T) { func TestVerifyingInvocationsCountReturnsThePerformedCalls(t *testing.T) { // Arrange testState := NewTestingMock(t) - mockedApi := Api(testState) - defer func() { mockedApi.Close() }() + mockedAPI := API(testState) + defer func() { mockedAPI.Close() }() endpoint := "/endpoint" - endpointUrl := mockedApi.GetUrl().String() + endpoint - mockedApi. + endpointURL := mockedAPI.GetURL().String() + endpoint + mockedAPI. Stub(http.MethodPost, endpoint). WithStatusCode(http.StatusOK) client := http.Client{} - _, _ = client.Post(endpointUrl, "application/json", bytes.NewBuffer([]byte(`{"foo": "bar"}`))) - _, _ = client.Post(endpointUrl, "text/plain", bytes.NewBuffer([]byte("Hello"))) + _, _ = client.Post(endpointURL, "application/json", bytes.NewBuffer([]byte(`{"foo": "bar"}`))) + _, _ = client.Post(endpointURL, "text/plain", bytes.NewBuffer([]byte("Hello"))) // Act - calls := mockedApi.Verify(http.MethodPost, endpoint).HasBeenCalled(2) + calls := mockedAPI.Verify(http.MethodPost, endpoint).HasBeenCalled(2) // Assert testState.assertDidNotFailed() @@ -79,7 +80,7 @@ func TestVerifyingInvocationsCountReturnsThePerformedCalls(t *testing.T) { call1Content := struct { Foo string `json:"foo"` }{} - call1.ReadJsonPayload(&call1Content) + call1.ReadJSONPayload(&call1Content) assert.Equal("bar", call1Content.Foo) call2 := calls[1] @@ -91,38 +92,37 @@ func TestVerifyingInvocationsCountReturnsThePerformedCalls(t *testing.T) { func TestVerifyingSingleInvocationPasses(t *testing.T) { // Arrange testState := NewTestingMock(t) - mockedApi := Api(testState) - defer func() { mockedApi.Close() }() + mockedAPI := API(testState) + defer func() { mockedAPI.Close() }() - mockedApi. + mockedAPI. Stub(http.MethodGet, "/endpoint"). WithStatusCode(http.StatusOK) // Act client := http.Client{} - _, _ = client.Get(mockedApi.GetUrl().String() + "/endpoint") - mockedApi.Verify(http.MethodGet, "/endpoint").HasBeenCalledOnce() + _, _ = client.Get(mockedAPI.GetURL().String() + "/endpoint") + mockedAPI.Verify(http.MethodGet, "/endpoint").HasBeenCalledOnce() // Assert testState.assertDidNotFailed() - } func TestVerifyingSingleInvocationFails(t *testing.T) { // Arrange testState := NewTestingMock(t) - mockedApi := Api(testState) - defer func() { mockedApi.Close() }() + mockedAPI := API(testState) + defer func() { mockedAPI.Close() }() - mockedApi. + mockedAPI. Stub(http.MethodGet, "/endpoint"). WithStatusCode(http.StatusOK) // Act client := http.Client{} - _, _ = client.Get(mockedApi.GetUrl().String() + "/endpoint") - _, _ = client.Get(mockedApi.GetUrl().String() + "/endpoint") - mockedApi.Verify(http.MethodGet, "/endpoint").HasBeenCalledOnce() + _, _ = client.Get(mockedAPI.GetURL().String() + "/endpoint") + _, _ = client.Get(mockedAPI.GetURL().String() + "/endpoint") + mockedAPI.Verify(http.MethodGet, "/endpoint").HasBeenCalledOnce() // Assert testState.assertFailedWithFatal() @@ -131,19 +131,19 @@ func TestVerifyingSingleInvocationFails(t *testing.T) { func TestVerifyingSingleInvocationReturnsThePerformedCall(t *testing.T) { // Arrange testState := NewTestingMock(t) - mockedApi := Api(testState) - defer func() { mockedApi.Close() }() + mockedAPI := API(testState) + defer func() { mockedAPI.Close() }() endpoint := "/endpoint" - endpointUrl := mockedApi.GetUrl().String() + endpoint - mockedApi. + endpointURL := mockedAPI.GetURL().String() + endpoint + mockedAPI. Stub(http.MethodPost, endpoint). WithStatusCode(http.StatusOK) client := http.Client{} - _, _ = client.Post(endpointUrl, "text/plain", bytes.NewBuffer([]byte("Hello"))) + _, _ = client.Post(endpointURL, "text/plain", bytes.NewBuffer([]byte("Hello"))) // Act - _ = mockedApi. + _ = mockedAPI. Verify(http.MethodPost, endpoint). HasBeenCalledOnce(). WithStringPayload("Hello").