h2non / gock

HTTP traffic mocking and testing made easy in Go ༼ʘ̚ل͜ʘ̚༽
https://pkg.go.dev/github.com/h2non/gock
MIT License
2.04k stars 106 forks source link

How to use Gock with Parallel tests #117

Open sazzer opened 3 months ago

sazzer commented 3 months ago

I'll openly admit I'm not the most proficient Go user. As such, I make heavy use of tools like golangci-lint to validate my code.

One thing this constantly flags up is when I forget t.Parallel() in my tests. However, I've also noticed that if I am using this then my gock-based tests fail.

For example:

package application_test

import (
    "net/http"
    "testing"

    "github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/require"
    "gopkg.in/h2non/gock.v1"
)

func TestSimple(t *testing.T) {
    t.Parallel()

    defer gock.Off()

    gock.New("http://foo.com").
        Get("/bar").
        Reply(200)

    res, err := http.Get("http://foo.com/bar")

    require.NoError(t, err)
    assert.Equal(t, http.StatusOK, res.StatusCode)

    res.Body.Close()

    assert.True(t, gock.IsDone())
}

func TestSimple2(t *testing.T) {
    t.Parallel()

    defer gock.Off()

    gock.New("http://foo.com").
        Get("/bar").
        Reply(400)

    res, err := http.Get("http://foo.com/bar")

    require.NoError(t, err)
    assert.Equal(t, http.StatusBadRequest, res.StatusCode)

    res.Body.Close()

    assert.True(t, gock.IsDone())
}

This test file will sometimes pass, and will sometimes fail. And that's not surprising, because Gock has a bunch of static state that's shared between tests, and the tests are running in parallel.

Obviously removing t.Parallel() will make this work, but that seems a shame. I've also seen a suggestion of writing a TestMain() function that does all of the Gock work, but that also seems overly complicated for what's needed.

Are there any other recommended ways of doing tests like this?

Cheers

nash567 commented 1 month ago

@sazzer, one workaround you can do is to create separate gock request instances for each test case and flush it off when the test case is executed.

eg.

type institution struct { ID string ShortName string }

type institutionResponse struct { Institutions []institution }

func NewTestGock() (*gock.Request, string) { baseURL := "https://localhost:63564" return gock.New(baseURL), baseURL }

` func TestClient_Get(t *testing.T) { t.Parallel() expectedResp := institutionResponse{ Institutions: []institution{ {ID: "1033", ShortName: "CommBank"}}, }

type args struct {
    ctx  context.Context
    path string
}
tests := []struct {
    name    string
    args    args
    want    []byte
    wantErr bool
}{
    {
        name: "Success",
        args: args{
            ctx:  metadata.NewOutgoingContext(context.Background(), metadata.Pairs(interceptors.AuthMetadataKey, "token-value")),
            path: "v1/Finances/institutions",
        },
        want: func() []byte {
            data, _ := json.Marshal(expectedResp)
            return data
        }(),
        wantErr: false,
    },
    {
        name: "Unauthorized",
        args: args{
            ctx:  context.Background(),
            path: "v1/Finances/institutions",
        },
        want:    nil,
        wantErr: true,
    },
}
for _, tC := range tests {
    tt := tC
    t.Run(tt.name, func(t *testing.T) {
        t.Parallel()
        req, url := NewTestGock()
        defer gock.Remove(req.Mock)
        req.Get("v1/Finances/institutions").
            HeaderPresent("Authorization").
            Reply(200).
            JSON(expectedResp)
        c := coreapi.NewClient(&config.CoreAPI{
            BaseURL: url,
        })
        got, err := c.Get(tt.args.ctx, tt.args.path)
        if (err != nil) != tt.wantErr {
            t.Errorf("Get() error = %v, wantErr %v", err, tt.wantErr)
            return
        }
        if !reflect.DeepEqual(bytes.TrimRight(got, "\n"), bytes.TrimRight(tt.want, "\n")) {
            t.Errorf("Get() got = %v, want %v", got, tt.want)
        }
    })
}

}`

steffakasid commented 3 weeks ago

Doesn't work for me...

I just disabled the t.Parrallel() for the few cases I had...