From d5fcd330144b6faacdaee59a92469bfdb46c4eb8 Mon Sep 17 00:00:00 2001 From: Ritik Jain <60597329+re-Tick@users.noreply.github.com> Date: Thu, 9 Feb 2023 16:27:20 +0530 Subject: [PATCH] docs(mock): adds docs for mocking with unit-tests (#173) * docs(mock): adds docs for mocking with unit-tests Signed-off-by: re-Tick * docs(mock): updates docs for mocks/stubs for unit-tests Signed-off-by: re-Tick --------- Signed-off-by: re-Tick --- README.md | 115 +++++++++++++++++++++---- integrations/khttpclient/httpClient.go | 2 +- mock/mock.go | 10 +-- 3 files changed, 106 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index ddd9977..ad7af8d 100644 --- a/README.md +++ b/README.md @@ -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 @@ -11,28 +11,29 @@ 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: "", // 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: "", // optional. relative(./internals) or absolute(/users/xyz/...) + Name: "", // 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: "", // optional. It can be relative(./internals) or absolute(/users/xyz/...) CTX: , // optional. can be used to pass existing running context. }) ... @@ -40,16 +41,16 @@ ctx := mock.NewContext(mock.Config{ 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: "", // 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: "", // optional. relative(./internals) or absolute(/users/xyz/...) - CTX: , // optional. can be used to pass existing running context. + Name: "", // Should be unique for every mock/stub. Its compulsory during tests. + Mode: keploy.MODE_TEST, + Path: "", // Optional. It should be the path to the recorded mocks/stubs.It can be relative(./internals) or absolute(/users/xyz/...) + CTX: , // 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 @@ -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: ".." + Replace: map[string]string{}, // replaces values of given http fields. Format: key (can be "header.", "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) + } + } +} + +``` \ No newline at end of file diff --git a/integrations/khttpclient/httpClient.go b/integrations/khttpclient/httpClient.go index 3182ea1..388f656 100644 --- a/integrations/khttpclient/httpClient.go +++ b/integrations/khttpclient/httpClient.go @@ -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, } diff --git a/mock/mock.go b/mock/mock.go index 876330e..584fb48 100644 --- a/mock/mock.go +++ b/mock/mock.go @@ -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: ".." Replace map[string]string // replaces values of given http fields. Format: key (can be "header.", "domain", "method", "proto_major", "proto_minor") }