echoprobe is a simple Go library for writing integration tests in for echo framework. It uses test-containers and gock to provide a simple and easy-to-use interface to write tests. supporting real HTTP requests and responses in various formats.
- Real HTTP requests and responses in
JSONandExcelformats - Mocking of external HTTP calls
- Database integrations with
PostgreSQLandBigQuery - Fully compatible with fastecho
Install echoprobe, using go get:
$ go get github.com/ingka-group/echoprobeBelow you can find a list of examples in order to use echoprobe.
- Basic usage
- With PostgreSQL
- With BigQuery
- With Mocks
- With PostgreSQL and Mocks
- With Excel
- Error responses
- Query parameters
- Request body
- Assert with custom context
You can find some complete examples in the test directory.
To write an integration test, you need to create a new test file with the _test.go suffix. In case your test cases of your handlers expect a response, you can leverage the fixtures feature. Fixtures are optional, and are required only in case you expect a response body. A fixtures folder has to exist in the location where the _test.go files are so that files can be read. This also ensures that fixtures are kept close to the test and in the relevant package.
To store the expected responses, you need to create a responses directory under fixtures to store your JSON responses. For example, fixtures/responses/my_response.json.
it := echoprobe.NewIntegrationTest(
t,
)
defer func() {
it.TearDown()
}()
handler := NewHandler()
tests := []echoprobe.Data{
{
Name: "ok: my test case",
Method: http.MethodGet,
Params: echoprobe.Params {
Path: map[string]string {
"id": "1",
},
},
Handler: handler.MyEndpoint,
ExpectCode: http.StatusOK,
ExpectResponse: "my_response",
},
}
echoprobe.AssertAll(it, tests)To use PostgreSQL in your integration test, you need to pass the IntegrationTestWithPostgres option to the NewIntegrationTest function. Optionally, you can initialize your database using a SQL script, that will be executed before the test starts. The script should contain the necessary DDL and DML statements to prepare the database for the test. The script must be present under fixtures. For example, fixtures/init-db.sql.
it := echoprobe.NewIntegrationTest(
t,
echoprobe.IntegrationTestWithPostgres{
InitSQLScript: "init-db.sql",
},
)
defer func() {
it.TearDown()
}()
repository := NewRepository(it.Db)
service := NewService(repository)
handler := NewHandler(service)
tests := []echoprobe.Data{...}
echoprobe.AssertAll(it, tests)echoprobe supports testing with BigQuery using ghcr.io/goccy/bigquery-emulator as a test contair. To use BigQuery in your integration test, you need to pass the IntegrationTestWithBigQuery option to the NewIntegrationTest function. It is expected that BigQuery needs to be populated with data upon the test startup. To do that, you need to provide a .yaml under the fixtures/bigquery directory.
The YAML file should contain the necessary format so that BigQuery emulator can mount the data in the container.
it := echoprobe.NewIntegrationTest(
t,
echoprobe.IntegrationTestWithBigQuery{
DataPath: "/fixtures/bigquery/data.yaml",
},
)
bqClient, err := NewBigQueryClient(it.BigQuery)
repository := NewRepository(bqClient)
service := NewService(repository)
handler:= NewHandler(service)
tests := []echoprobe.Data{...}
echoprobe.AssertAll(it, tests)
func NewBigQueryClient(t *testing.T, bq *echoprobe.BigqueryEmulatorContainer) (*bigquery.Client, error) {
client, err := bigquery.NewClient(
context.Background(),
"test",
option.WithoutAuthentication(),
option.WithEndpoint(fmt.Sprintf("http://%s:%d", bq.BqHost, bq.BqRestPort)),
)
defer func(client *bigquery.Client) {
err := client.Close()
if err != nil {
t.Fatalf("unable to close big query client: %v", err)
}
}(client)
return client, err
}Mock responses are optional and must be stored with the rest of the fixtures as .json files in a mocks folder within fixtures. For example, a mock called my_mock.json would be stored in fixtures/mocks/my_mock.json. Mocking a request, consists of pairing a request URL with a status code and optionally a response.
NOTE: Mocks are single-use. If your code repeatedly calls the same endpoint expecting the same response, writing this once is not enough. You need to replicate the mock itself to make a request reuse a mock a specific number of times.
it := echoprobe.NewIntegrationTest(
t,
echoprobe.IntegrationTestWithMocks{
BaseURL: "/v1",
},
)
defer func() {
it.TearDown()
}()
handler := NewHandler()
tests := []echoprobe.Data{
{
Name: "ok: my test case",
Method: http.MethodGet,
Params: echoprobe.Params {
Path: map[string]string {
"id": "1",
},
},
Mocks: []echoprobe.MockCall{
{
Config: &echoprobe.MockConfig{
UrlPath: fmt.Sprintf("/v1/users/%s", "1"),
Response: "my_mock",
StatusCode: http.StatusOK,
},
},
},
Handler: handler.MyEndpoint,
ExpectCode: http.StatusOK,
ExpectResponse: "my_response",
},
}
echoprobe.AssertAll(it, tests)By default, echoprobe only listens to the baseUrl on the http.DefaultClient.
When using a custom http.Client for HTTP calls, echoprobe should be notified.
The custom client can be added after initializing the integration tests:
it := echoprobe.NewIntegrationTest(
t,
echoprobe.IntegrationTestWithMocks{
BaseURL: "/v1",
},
)
client := http.Client{Transport: &http.Transport{}}
it.Mock.SetHttpClient(&client)An integration test support various types of features all at once. In order to use PostgreSQL and Mocks, you can use the following example. Similarly, you can append other options to your integration test.
it := echoprobe.NewIntegrationTest(
t,
echoprobe.IntegrationTestWithMocks{
BaseURL: "/v1",
},
echoprobe.IntegrationTestWithPostgres{},
)echoprobe supports testing with Excel files. To compare the result of a handler with the expected Excel file, you need to store the Excel file(s) under excel in the fixtures folder. For example, fixtures/excel/my_excel.xlsx.
Additionally, you need to pass some extra instructions to the test case, to identify that the response is expected to be an Excel file.
tests := []echoprobe.Data{
{
Name: "ok: my test case",
Method: http.MethodGet,
Params: echoprobe.Params {
Path: map[string]string {
"id": "1",
},
},
Handler: handler.MyEndpoint,
ExpectCode: http.StatusOK,
ExpectResponseType: echoprobe.Excel,
ExpectResponse: "my_excel",
},
}echoprobe is fully compatible with the echo.NewHTTPError response, meaning that you can test error responses as well
by setting the ExpectErrResponse field to true in the test data.
tests := []echoprobe.Data{
{
Name: "error: my test case",
Method: http.MethodGet,
Params: echoprobe.Params {
Path: map[string]string {
"id": "INVALID_ID",
},
},
Handler: handler.MyEndpoint,
ExpectCode: http.StatusBadRequest,
ExpectErrResponse: true,
},
}echoprobe supports also query parameters in the request. You can pass them in the Params of the Data struct. The structure supports multiple query parameters with the same key to cover situations where the endpoint supports multiple values for the same parameter.
tests := []echoprobe.Data{
{
Name: "ok: my test case",
Method: http.MethodGet,
Params: echoprobe.Params {
Body: "my_body",
Query: map[string][]string {
"param1": {"value1", "value2"},
}
},
Handler: handler.MyEndpoint,
ExpectCode: http.StatusNoContent,
},
}In case your request required a body, you can pass it in the Data struct. In such cases, you need to store the JSON of the request body under requests in the fixtures folder. For example, fixtures/requests/my_body.json.
tests := []echoprobe.Data{
{
Name: "ok: my test case",
Method: http.MethodPost,
Params: echoprobe.Params {
Body: "my_body",
},
Handler: handler.MyEndpoint,
ExpectResponse: "my_response",
ExpectCode: http.StatusCreated,
},
}echoprobe provides the function AssertAll to assert the test cases. In case you need to assert the test cases with a custom context, you can create your own function for that.
// AssertAllWithCustomContext is a helper function to run multiple tests in a single test function.
// Before asserting a test, the function prepares the custom context and calls the handler function.
func AssertAllWithCustomContext(it *echoprobe.IntegrationTest, tt []echoprobe.Data) {
for _, t := range tt {
ctx, response := echoprobe.Request(it, t.Method, t.Params)
// Create the custom context
sctx := &CustomContext{
Context: ctx,
Clock: clock.NewMock(),
}
// Attach the custom context to the handler
err := t.Handler(sctx)
if err != nil {
it.T.Log(err.Error())
}
// Assert the test
echoprobe.Assert(it, &t, &echoprobe.HandlerResult{
Err: err,
Response: response,
})
}
}To run the full set of tests you can execute the following command.
$ make testThis project uses pre-commit(https://pre-commit.com/) to integrate code checks used to gate commits.
# required only once
$ pre-commit install
pre-commit installed at .git/hooks/pre-commit
# run checks on all files
$ make pre-commitFor more information on the available targets, you can access the inline help of the Makefile.
$ make helpor, equivalently,
$ makePlease read CONTRIBUTING for more details about making a contribution to this open source project and ensure that you follow our CODE_OF_CONDUCT.
If you have any other issues or questions regarding this project, feel free to contact one of the code owners/maintainers for a more in-depth discussion.
This open source project is licensed under the "Apache-2.0", read the LICENCE terms for more details.