Skip to content

Commit

Permalink
docs(mock): adds docs for mocking with unit-tests (#173)
Browse files Browse the repository at this point in the history
* docs(mock): adds docs for mocking with unit-tests

Signed-off-by: re-Tick <jain.ritik.1001@gmail.com>

* docs(mock): updates docs for mocks/stubs for unit-tests

Signed-off-by: re-Tick <jain.ritik.1001@gmail.com>

---------

Signed-off-by: re-Tick <jain.ritik.1001@gmail.com>
  • Loading branch information
re-Tick authored Feb 9, 2023
1 parent e5b7caa commit d5fcd33
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 21 deletions.
115 changes: 100 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Keploy Go-SDK
[![PkgGoDev](https://pkg.go.dev/badge/github.com/keploy/go-sdk#readme-contents)](https://pkg.go.dev/github.com/keploy/go-sdk#readme-contents)

This is the client SDK for the [Keploy](https://github.com/keploy/keploy) testing platform. You can use this to generate realistic mock files or entire e2e tests for your applications. The **HTTP mocks and tests are the same format** are are inter-exchangeable.
This is the client SDK for the [Keploy](https://github.com/keploy/keploy) testing platform. You can use this to generate realistic mock files or entire e2e tests for your applications. The **HTTP mocks/stubs and tests are the same format** are are inter-exchangeable.

## Contents

Expand All @@ -11,45 +11,46 @@ This is the client SDK for the [Keploy](https://github.com/keploy/keploy) testin
4. [Supported Databases](#supported-databases)
5. [Support Clients](#supported-clients)
6. [Supported JWT Middlewares](#supported-jwt-middlewares)
7. [Mocking/Stubbing for unit tests](#mockingstubbing-for-unit-tests)

## Installation
```bash
go get -u github.com/keploy/go-sdk
```
## Usage
### Create mocks by recording external calls (APIs, DBs..)
These mocks are realistic and frees you up from writing mocks manually. Keploy creates files which can be referenced in any of your tests.
1. **Wrap your depedency**: To record your depdencies, you need to wrap their clients with the supported keploy wrapper as metioned below. If your dependency is not supported please open a [feature request.](https://github.com/keploy/go-sdk/issues/new?assignees=&labels=&template=feature_request.md&title=)
2. **Record**: To record you can import the keploy mocking library and set the mode to record mode. This should generate a file containing the mocks.
### Create mocks/stubs for your unit-test
These mocks/stubs are realistic and frees you up from writing them manually. Keploy creates `readable/editable` mocks/stubs yaml files which can be referenced in any of your unit-tests tests. An example is mentioned in [Mocking/Stubbing for unit tests](#mockingstubbing-for-unit-tests) section
1. **Wrap your depedency**: To wrap your depdencies, you need to integrate them with the keploy supported wrappers as metioned below. If your dependency is not supported please open a [feature request.](https://github.com/keploy/go-sdk/issues/new?assignees=&labels=&template=feature_request.md&title=)
2. **Record**: To record you can import the keploy mocking library and set the mode to record mode. This should generate a file containing the mocks/stubs.
```go
import(
"github.com/keploy/go-sdk/keploy"
"github.com/keploy/go-sdk/mock"
)

// inside your test
// Inside your unit test
...
ctx := mock.NewContext(mock.Config{
Name: "<name_for_mocks>", // unique for every mock. if you dont provide during record it would be generated. Its compulsory during tests.
Mode: keploy.MODE_RECORD, // Or keploy.MODE_TEST, Or keploy.MODE_OFF. Default is keploy.MODE_OFF
Path: "<local_path_for_yaml>", // optional. relative(./internals) or absolute(/users/xyz/...)
Name: "<name_for_yamls>", // It is unique for every mock/stub. If you dont provide during record it would be generated. Its compulsory during tests.
Mode: keploy.MODE_RECORD, // It can be MODE_TEST or MODE_OFF. Default is MODE_TEST
Path: "<local_path_for_yaml>", // optional. It can be relative(./internals) or absolute(/users/xyz/...)
CTX: <existing context>, // optional. can be used to pass existing running context.
})
...
```
3. **Mock**: To mock dependency as per the content of the generated file (during testing) - just set the `Mode` config to `keploy.MODE_TEST` or remove the variable all together (default is test mode). eg:
```go
ctx := mock.NewContext(mock.Config{
Name: "<name_for_mocks>", // unique for every mock. if you dont provide during record it would be generated. Its compulsory during tests.
Mode: keploy.MODE_TEST, // Or keploy.MODE_TEST, Or keploy.MODE_OFF. Default is keploy.MODE_OFF
Path: "<local_path_for_yaml>", // optional. relative(./internals) or absolute(/users/xyz/...)
CTX: <existing context>, // optional. can be used to pass existing running context.
Name: "<name_for_yamls>", // Should be unique for every mock/stub. Its compulsory during tests.
Mode: keploy.MODE_TEST,
Path: "<local_path_for_yaml>", // Optional. It should be the path to the recorded mocks/stubs.It can be relative(./internals) or absolute(/users/xyz/...)
CTX: <existing context>, // Optional. It can be used to pass existing running context.
})
```


### Generate E2E tests (with mocks)
These tests can be run alongside your manually written `go-tests`and adds coverage to them. This way you can focus on only writing those tests that are hard to e2e test with keploy. Mocks are also generated and linked to their respective tests. As mentioned above, the tests can also be shared with the clients for mocking (and vice versa!).
### Generate E2E tests
These tests can be run alongside your manually written `go-tests`and adds coverage to them. This way you can focus on only writing those tests that are hard to e2e test with keploy. Mocks/stubs are also generated and linked to their respective tests. As mentioned above, the tests can also be shared with the clients for mocking (and vice versa!).
1. **Wrap your depedency**: (same as above)
2. **Intialize SDK**: You would need to initialize the keploy SDK
```go
Expand Down Expand Up @@ -783,3 +784,87 @@ func ginRouter() http.Handler {
return gr
}
```

## Mocking/Stubbing for unit tests
Mocks/Stubs can be generated for external dependency calls of go unit tests as `readable/editable` yaml files using Keploy.


### Example
```go
import (
"bytes"; "context"; "io"; "net/http"; "testing"

"github.com/go-test/deep"
"github.com/keploy/go-sdk/integrations/khttpclient"
"github.com/keploy/go-sdk/keploy"
"github.com/keploy/go-sdk/mock"
)

func TestExample(t *testing.T) {
for _, tt := range []struct {
input struct {
tcsName string
reqURL string
}
output struct {
resp string
err error
}
}{
{
input: struct {
tcsName string
reqURL string
}{
tcsName: "sample-mock",
reqURL: "https://api.keploy.io/healthz",
},
output: struct {
resp string
err error
}{
resp: "ok",
err: nil,
},
},
} {
ctx := context.Background()
// Integration for keploy unit-tests mocks/stubs.
ctx = mock.NewContext(mock.Config{
Mode: keploy.MODE_RECORD, // Keploy mode on which unit test will run. Possible values: MODE_TEST or MODE_RECORD.
Name: tt.input.tcsName, // Unique names for testcases of a unit test. If it is empty then, a unique id is generated by keploy during RECORD.
CTX: ctx, // Context in which KeployContext will be stored. If it is nil, then KeployContext is stored in context.Background.
Path: "./", // Path in which Keploy "/mocks" will be generated. Default: current working directroy.
OverWrite: false, // OverWrite is used to compare new outputs of external calls with previously recorded outputs during record.
Remove: []string{}, // removes given http fields from yaml. Format: "<req_OR_resp_OR_all>.<header_OR_body>.<FIELD_NAME>"
Replace: map[string]string{}, // replaces values of given http fields. Format: key (can be "header.<KEY>", "domain", "method", "proto_major", "proto_minor")
})

// Integration for Keploy supported httpClient
interceptor := khttpclient.NewInterceptor(http.DefaultTransport)
client := http.Client{
Transport: interceptor,
}
// Http request with generated context
req, err := http.NewRequestWithContext(ctx, "GET", tt.input.reqURL, bytes.NewBuffer([]byte{}))
if err != nil {
t.Error("failed to make http request", err)
}
// Make the http call
resp, actErr := client.Do(req)
if deep.Equal(tt.output.err, actErr) != nil {
t.Fatal("Testcase Failed. Expected Error:", tt.output.err, " Actual Error: ", actErr)
}
defer resp.Body.Close()
bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
t.Error("failed to read response body", err)
}
actRespBody := string(bodyBytes)
if deep.Equal(tt.output.resp, actRespBody) != nil {
t.Fatal("Testcase Failed. Expected Response body:", tt.output.resp, " Actual Response body: ", actRespBody)
}
}
}

```
2 changes: 1 addition & 1 deletion integrations/khttpclient/httpClient.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ func (i Interceptor) RoundTrip(r *http.Request) (*http.Response, error) {
}
mode := kctx.Mode
meta := map[string]string{
"name": "http-client",
"name": "Http",
"type": string(models.HttpClient),
"operation": r.Method,
}
Expand Down
10 changes: 5 additions & 5 deletions mock/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ var (
)

type Config struct {
Mode keploy.Mode // Default: test
Name string // Default: Auto generated during record mode and required in test mode
CTX context.Context
Path string // Default: ./mocks
OverWrite bool
Mode keploy.Mode // Keploy mode on which unit test will run. Possible values: MODE_TEST or MODE_RECORD. Default: MODE_TEST
Name string // Unique names for testcases of a unit test. If it is empty then, a unique id is generated by keploy during RECORD.
CTX context.Context // Context in which KeployContext will be stored. If it is nil, then KeployContext is stored in context.Background.
Path string // Path in which Keploy "/mocks" will be generated. Default: current working directroy.
OverWrite bool // OverWrite is used to compare new outputs of external calls with previously recorded outputs during record.
Remove []string // removes given http fields from yaml. Format: "<req_OR_resp_OR_all>.<header_OR_body>.<FIELD_NAME>"
Replace map[string]string // replaces values of given http fields. Format: key (can be "header.<KEY>", "domain", "method", "proto_major", "proto_minor")
}
Expand Down

0 comments on commit d5fcd33

Please sign in to comment.