Giter Site home page Giter Site logo

steinfletcher / apitest Goto Github PK

View Code? Open in Web Editor NEW
738.0 12.0 55.0 2.2 MB

A simple and extensible behavioural testing library for Go. You can use api test to simplify REST API, HTTP handler and e2e tests.

Home Page: https://apitest.dev

License: MIT License

Go 99.86% Makefile 0.14%
golang testing api-testing blackbox-testing behavioural-tests jsonpath mocking mocks sequence-diagrams

apitest's Introduction

Godoc Build Status Go Report Card Mentioned in Awesome Go

apitest

A simple and extensible behavioural testing library. Supports mocking external http calls and renders sequence diagrams on completion.

In behavioural tests the internal structure of the app is not known by the tests. Data is input to the system and the outputs are expected to meet certain conditions.

Join the conversation at #apitest on https://gophers.slack.com.

Logo by @egonelbre

Note: The API for apitest is stable and complete - despite the lack of activity this repository is still actively maintained. Any new issues will be addressed. Feature requests will be considered.

Documentation

Please visit https://apitest.dev for the latest documentation.

Installation

go get -u github.com/steinfletcher/apitest

Demo

animated gif

Examples

Framework and library integration examples

Example Comment
gin popular martini-like web framework
graphql using gqlgen.com to generate a graphql server
gorilla the gorilla web toolkit
iris iris web framework
echo High performance, extensible, minimalist Go web framework
fiber Express inspired web framework written in Go
httprouter High performance HTTP request router that scales well
mocks example mocking out external http calls
sequence diagrams generate sequence diagrams from tests. See the demo
Ginkgo Ginkgo BDD test framework

Companion libraries

Library Comment
JSONPath JSONPath assertion addons
CSS Selectors CSS selector assertion addons
PlantUML Export sequence diagrams as plantUML
DynamoDB Add DynamoDB interactions to sequence diagrams

Credits

This library was influenced by the following software packages:

  • YatSpec for creating sequence diagrams from tests
  • MockMVC and superagent for the concept and behavioural testing approach
  • Gock for the approach to mocking HTTP services in Go
  • Baloo for API design

Code snippets

JSON body matcher

func TestApi(t *testing.T) {
	apitest.New().
		Handler(handler).
		Get("/user/1234").
		Expect(t).
		Body(`{"id": "1234", "name": "Tate"}`).
		Status(http.StatusOK).
		End()
}

JSONPath

For asserting on parts of the response body JSONPath may be used. A separate module must be installed which provides these assertions - go get -u github.com/steinfletcher/apitest-jsonpath. This is packaged separately to keep this library dependency free.

Given the response is {"a": 12345, "b": [{"key": "c", "value": "result"}]}

func TestApi(t *testing.T) {
	apitest.Handler(handler).
		Get("/hello").
		Expect(t).
		Assert(jsonpath.Contains(`$.b[? @.key=="c"].value`, "result")).
		End()
}

and jsonpath.Equals checks for value equality

func TestApi(t *testing.T) {
	apitest.Handler(handler).
		Get("/hello").
		Expect(t).
		Assert(jsonpath.Equal(`$.a`, float64(12345))).
		End()
}

Custom assert functions

func TestApi(t *testing.T) {
	apitest.Handler(handler).
		Get("/hello").
		Expect(t).
		Assert(func(res *http.Response, req *http.Request) error {
			assert.Equal(t, http.StatusOK, res.StatusCode)
			return nil
		}).
		End()
}

Assert cookies

func TestApi(t *testing.T) {
	apitest.Handler(handler).
		Patch("/hello").
		Expect(t).
		Status(http.StatusOK).
		Cookies(apitest.Cookie("ABC").Value("12345")).
		CookiePresent("Session-Token").
		CookieNotPresent("XXX").
		Cookies(
			apitest.Cookie("ABC").Value("12345"),
			apitest.Cookie("DEF").Value("67890"),
		).
		End()
}

Assert headers

func TestApi(t *testing.T) {
	apitest.Handler(handler).
		Get("/hello").
		Expect(t).
		Status(http.StatusOK).
		Headers(map[string]string{"ABC": "12345"}).
		End()
}

Mocking external http calls

var getUser = apitest.NewMock().
	Get("/user/12345").
	RespondWith().
	Body(`{"name": "jon", "id": "1234"}`).
	Status(http.StatusOK).
	End()

var getPreferences = apitest.NewMock().
	Get("/preferences/12345").
	RespondWith().
	Body(`{"is_contactable": true}`).
	Status(http.StatusOK).
	End()

func TestApi(t *testing.T) {
	apitest.New().
		Mocks(getUser, getPreferences).
		Handler(handler).
		Get("/hello").
		Expect(t).
		Status(http.StatusOK).
		Body(`{"name": "jon", "id": "1234"}`).
		End()
}

Generating sequence diagrams from tests

func TestApi(t *testing.T) {
	apitest.New().
		Report(apitest.SequenceDiagram()).
		Mocks(getUser, getPreferences).
		Handler(handler).
		Get("/hello").
		Expect(t).
		Status(http.StatusOK).
		Body(`{"name": "jon", "id": "1234"}`).
		End()
}

It is possible to override the default storage location by passing the formatter instance Report(apitest.NewSequenceDiagramFormatter(".sequence-diagrams")). You can bring your own formatter too if you want to produce custom output. By default a sequence diagram is rendered on a html page. See the demo

Debugging http requests and responses generated by api test and any mocks

func TestApi(t *testing.T) {
	apitest.New().
		Debug().
		Handler(handler).
		Get("/hello").
		Expect(t).
		Status(http.StatusOK).
		End()
}

Provide basic auth in the request

func TestApi(t *testing.T) {
	apitest.Handler(handler).
		Get("/hello").
		BasicAuth("username", "password").
		Expect(t).
		Status(http.StatusOK).
		End()
}

Pass a custom context to the request

func TestApi(t *testing.T) {
	apitest.Handler(handler).
		Get("/hello").
		WithContext(context.TODO()).
		Expect(t).
		Status(http.StatusOK).
		End()
}

Provide cookies in the request

func TestApi(t *testing.T) {
	apitest.Handler(handler).
		Get("/hello").
		Cookies(apitest.Cookie("ABC").Value("12345")).
		Expect(t).
		Status(http.StatusOK).
		End()
}

Provide headers in the request

func TestApi(t *testing.T) {
	apitest.Handler(handler).
		Delete("/hello").
		Headers(map[string]string{"My-Header": "12345"}).
		Expect(t).
		Status(http.StatusOK).
		End()
}

Provide query parameters in the request

Query, QueryParams and QueryCollection can all be used in combination

func TestApi(t *testing.T) {
	apitest.Handler(handler).
		Get("/hello").
		QueryParams(map[string]string{"a": "1", "b": "2"}).
		Query("c", "d").
		Expect(t).
		Status(http.StatusOK).
		End()
}

Providing {"a": {"b", "c", "d"} results in parameters encoded as a=b&a=c&a=d. QueryCollection can be used in combination with Query

func TestApi(t *testing.T) {
	apitest.Handler(handler).
		Get("/hello").
		QueryCollection(map[string][]string{"a": {"b", "c", "d"}}).
		Expect(t).
		Status(http.StatusOK).
		End()
}

Provide a url encoded form body in the request

func TestApi(t *testing.T) {
	apitest.Handler(handler).
		Post("/hello").
		FormData("a", "1").
		FormData("b", "2").
		FormData("b", "3").
		FormData("c", "4", "5", "6").
		Expect(t).
		Status(http.StatusOK).
		End()
}

Provide a multipart/form-data

func TestApi(t *testing.T) {
	apitest.Handler(handler).
		Post("/hello").
		MultipartFormData("a", "1", "2").
		MultipartFile("file", "path/to/some.file1", "path/to/some.file2").
		Expect(t).
		Status(http.StatusOK).
		End()
}

Capture the request and response data

func TestApi(t *testing.T) {
	apitest.New().
		Observe(func(res *http.Response, req *http.Request, apiTest *apitest.APITest) {
			// do something with res and req
		}).
		Handler(handler).
		Get("/hello").
		Expect(t).
		Status(http.StatusOK).
		End()
}

Intercept the request

This is useful for mutating the request before it is sent to the system under test.

func TestApi(t *testing.T) {
	apitest.Handler(handler).
		Intercept(func(req *http.Request) {
			req.URL.RawQuery = "a[]=xxx&a[]=yyy"
		}).
		Get("/hello").
		Expect(t).
		Status(http.StatusOK).
		End()
}

Contributing

View the contributing guide.

apitest's People

Contributors

anthonyfong100 avatar billbell73 avatar charlesworth avatar chrisjharding avatar codacy-badger avatar de-perotti avatar dependabot-preview[bot] avatar emmanuelay avatar enrico5b1b4 avatar fergusstrange avatar haraldmosh avatar hatsunemiku3939 avatar jessbellon avatar joncfoo avatar maranqz avatar mikacodes avatar seabornlee avatar steinfletcher avatar w32blaster avatar webbgeorge avatar ytausch avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

apitest's Issues

Observe function set with ObserveMocks() overwritten in api tests with reports

When setting an observe function with ObserveMocks() and running an apitest with a reporter, the observe function is not called.
Failing test scenario detailed below

func TestMocks_ApiTest_SupportsObservingMocksWithReport(t *testing.T) {
	var observedMocks []*mockInteraction
	reporter := &RecorderCaptor{}
	observeMocksCalled := false

	getUser := NewMock().
		Get("http://localhost:8080").
		RespondWith().
		Status(http.StatusOK).
		Body("1").
		Times(2).
		End()

	getPreferences := NewMock().
		Get("http://localhost:8080").
		RespondWith().
		Status(http.StatusOK).
		Body("2").
		End()

	New().
		Report(reporter).
		ObserveMocks(func(res *http.Response, req *http.Request, a *APITest) {
			observeMocksCalled = true
			if res == nil || req == nil || a == nil {
				t.Fatal("expected request and response to be defined")
			}
			observedMocks = append(observedMocks, &mockInteraction{response: res, request: req})
		}).
		Mocks(getUser, getPreferences).
		Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			bytes1 := getUserData()
			bytes2 := getUserData()
			bytes3 := getUserData()

			w.Write(bytes1)
			w.Write(bytes2)
			w.Write(bytes3)
			w.WriteHeader(http.StatusOK)
		})).
		Get("/").
		Expect(t).
		Status(http.StatusOK).
		Body(`112`).
		End()

	assert.Equal(t, 3, len(observedMocks))
	assert.True(t, observeMocksCalled)
}

JSONpath should be provided as a separate package

To remove unecessary dependencies, provide a separate package which provides implementations of apitest.Assert for JSONPath. We should remove the JSONPath matcher from the apitest.Response struct.

The name set in apitest.New("some name") doesn't seem to show up in logging upon failure

Describe the bug
It seems that it is possible to set a name for a http request by calling apitest.New("some name") yet the name doesn't show up in any of the logging when there is a failure. Am I misunderstanding the use of this variable? Is there some other way to make it visible?

Steps to reproduce
Use apitest.New("some name")

Expected behavior
Seeing "some name" show up somewhere in the logs when there is a failure in the test.

Additional context
n/a

Better handling of mismatched mocks when defining `Times()`

Context

Mocks are one-time use - when a mock is matched and the response is returned the mock gets removed from the pool of available mocks. If you want a mock to return multiple times you can set the value using Times(2). It is desirable to have tests fail if the Times(2) function has been set but the mock has not been matched by the given value - 2 in this case.

One approach would be to make this behaviour global - to fail on any unmatched mocks - but that breaks backwards compatibility and users might face failing tests when upgrading.

Proposal

If the Times(N) value is set and the mock isn't matched N times - fail the test with an error highlighting that there are unmatched mocks. It might be desirable to make this work for Times(0) if that is simple to implement, but it is not the main focus of this issue.

Echo - Assert body contains ID (string)

Hi,
I started writing my tests with apitest.
Please advise if I could assert the response body that it contains the ID of the just inserted row.

return c.String(http.StatusCreated, strconv.FormatInt(user.ID, 10))

and test

		apitest.New().
			Handler(e).
			Method(http.MethodPost).
			URL(commons.EndpointNewUser).
			FormData("code", "AB").
			FormData("pass", "abcd").
			Expect(t).
			Status(http.StatusCreated).
			End()
	}

I did not find this case in examples.

Or the only option would be to send it as JSON?
With JSON what should I use? jsonpath.Present("ID") for a {"ID":1} response? ( I cannot place a number to equal as the ID is incremented at each insert.)

Thank you!

Provide an ability to define a remote address for a test

Is your feature request related to a problem? Please describe.
I didn't find a way to set the RemoteAddr when running tests which breaks my code where I need to split host and port.

Describe the solution you'd like
Provide a builder method that sets a custom address for the test.

Describe alternatives you've considered
I use http.Request to set the remote address as a workaround.

Cookie request and response functions are not consistent

The cookie matching experience is complicated and there are too many approaches. Provide two simple matching functions which will optionally assert on cookie attributes if they are present in the assertion.

  1. Remove func (r *Response) Cookies(cookies map[string]string) *Response and func (r *Response) HttpCookies(cookies []http.Cookie) *Response. Replace with func (r *Response) Cookies(cookies []http.Cookie) *Response. Only match on provided fields in http.Cookie.

  2. Add a simple function which matches a cookie by key and value, e.g. Cookie("key", "value").

  3. Change request cookies to follow the same convention.

Note: 1) provides flexibility and 2) provides simplicity. We should keep CookieIsPresent and CookieIsNotPresent.

Note: The user should be able to use both Cookies(...) and Cookie(...) in combination.

Remove assert library

We want to remove dependency on github.com/stretchr/testify which will make this library dependency free.

Implement a simple approach to assert on values. I would be in favour of a couple of private methods in the test file to achieve this, which internally check conditions

add link to repo from docs

Maybe we should have a github icon in the header, or one of those fork banners. TBD

Context: We are writing better docs using hugo to replace the examples on in the README. When complete we'll host here: https://apitest.dev.

Discussion/Question: Roadmap for end-to-end tests ref https://apitest.dev/docs/config#networking

Referring https://apitest.dev/docs/config#networking

If you want to generate HTTP requests against a running application you should enable networking. This approach can be useful for performing end-to-end tests.

The end-to-end test support is mentioned in this particular section, but then the rest of the documentations are a bit disconnected on this(end-to-end) pov. E.g. mocks, and reports will not work with tests written with the EnableNetworking option- hoping this is the right understanding(?).

Is there any roadmap/to-do list to further enhance support for end-to-end tests? :) I would want to further discuss if there is interest.

Can we replace *testing T with an interface?

Is your feature request related to a problem? Please describe.
When using apitest to do testing of api, the Expect method takes in a *testing T pointer which is a concrete type. This makes interacting with other third party testing library like Ginkgo difficult.

Describe the solution you'd like
Replace the reliance on *testing.T with an interface.

From the developer of ginkg:


It looks like testify uses (*testing.T) directly instead of an interface (which gomock does). Ginkgo can provide such frameworks with a Fail function that one can wrap in such a way to satisfy most interfaces (this is how we support go mock), but *testing.T refers to a concrete type and passing in the testing.T for the test that Ginkgo runs in would cause the entire suite to fail should one of your mock assertions fail...


Describe alternatives you've considered
As per the section above another way would to just pass in the testing.T but would cause the entire suite to fail if one of the assertions fail.

Additional context
Other libraries such as testify have changed this to support ginkgo integration

I would be happy to create a PR if this is possible

Example for Fiber

Is your feature request related to a problem? Please describe.
Hi,
I started working with Fiber. It would be nice if one could have an example for Fiber.
Thank you!

Tests sometimes hang in CI

Is this an issue with travis or our test commands? Tests work fine locally.

If it's general travis flakiness, explore CircleCI, otherwise fix test scripts.

Site example

Describe the bug
Example ( https://github.com/steinfletcher/apitest-jsonpath ):

apitest.New().
	Handler(handler).
	Get("/hello").
	Expect(t).
	Assert(Present(`$.a`)).
	Assert(NotPresent(`$.password`)).
	End()

should be:

apitest.New().
	Handler(handler).
	Get("/hello").
	Expect(t).
	Assert(jsonpath.Present(`$.a`)).
	Assert(jsonpath.NotPresent(`$.password`)).
	End()

Better UX as people loking on site are just learning.

Update echo example to make it more clear

hi,
nice approach but too bad that for echo you would need the application to be written as per your way.
for me this limits the sharing of the echo logger and context.
note that this is different than the way labstack presented their use and also testing.
maybe you could ease up the handler so it can be prepared even if not using your app struct approach.

Add FAQs page

We want to include Q&A in the docs we are writing with Hugo. This ticket is to create a page, menu item and a basic questions. We can add more questions later - this ticket is really about the page and formatting.

We can also use this page for common troubleshooting issues.

option to output sequence diagram with custom name instead of hash

Is your feature request related to a problem? Please describe.
not really

Describe the solution you'd like
currently when apitest produce sequence diagram , it creates a file name by using hash.

meta["name"] = a.name
meta["hash"] = createHash(meta)

I want to produce the output with custom name such as "unit-test-feature-a" and probably we can just use the test name when we define the test name here

Describe alternatives you've considered
provide option so we can use custom name. Maybe additional param or boolean flag to use name

Additional context
Add any other context or screenshots about the feature request here.

Increase test coverage

Some branches are not covered by tests - see coveralls. This ticket is to add missing tests where appropriate.

Support multipart

Investigate with some tests to see if the current implementation supports this or if we need to add something.

Align mocks and assertion behaviour for `Header` method and support `HeaderRegexp`

The Header matcher in the mocks uses a regex, where the Header matcher in apitest assertions compares against a literal value.

I think we should change the mock matcher to also assert on a literal value, then support an additional HeaderRegexp method for working with regular expressions.

This is a breaking behaviour change so it should be planned for v2

Improve output formatting when JSON comparison fails in assertions

Given that an defined expectation does not match the test output, e.g. the json body response does not match the value returned by the application under test, it is difficult to identity which fields do not match.

Implement a solution that makes it easy to identity which parts of the json did not match. See testify for an example.

Sainsburys SmartShop bug report

Hey there, sorry for filing an issue here but I can't find a way to report a bug to Sainsburys and I saw you work there.

I signed up for a SmartShop account for their new store in Holborn.

The app accepted a signup with [email protected].

But the login API endpoint doesn't accept email addresses with a + sign.

Forgot password works.

Could you forward to the SmartShop team? I would create another account, but my Nectar card is now linked to the broken account.

Sorry again for the hijack. It would be great if Sainsburys had a bigger OSS presence.

Flow Testing in Table driven

Is your feature request related to a problem? Please describe.
Would like to test a flow in a table driven test with Gin. Testing the flow in a table driven test would make the test concise for me.

Describe the solution you'd like

func TestFlow(t *testing.T) {
	tt := []struct {
		name           string
		httpMethod     string
		request        string
		want           string
		statusCodeHTTP int
	}{
		{name: "t1", httpMethod: "GET", request: "/url1", want: `{"r1":128}`, statusCodeHTTP: http.StatusOK},
		{name: "t2", httpMethod: "PUT", request: "/url2", want: ``, statusCodeHTTP: http.StatusOK},
	}

	s := NewHTTPServer("0.0.0.0:8000")

	for _, tc := range tt {
		t.Run(tc.name, func(t *testing.T) {
			apitest.New().
				Handler(s.Engine).
                                (tc.httpMethod).(tc.request).
				Expect(t).
				Status(tc.statusCodeHTTP).
				Body(tc.want).
				End()
		})
	}
}

Below alternative is without chaining and does not look so good:

	for _, tc := range tt {
		t.Run(tc.name, func(t *testing.T) {
			v := apitest.New()
			v.Handler(s.Engine)
			v.Method(tc.httpMethod)
			v.Request().URL(tc.request)
			v.Request().Expect(t)
			v.Response().Status(tc.statusCodeHTTP)
			v.Request().Body(tc.want)
			v.Response().End()
		})
	}

Additional context
Everything works besides changing the HTTP request method and maintaining the nice chaining structure.

Add sequence diagram generator

I am working on another library to auto render sequence diagrams. The idea is to integrate into here when done. Will post some more details soon.

It should be possible to provide more than one custom Assert function

It is only possible to provide a single custom assert function. The library should support any number of assert functions, e.g.

apitest.New().
	Handler(newApp().Router).
	Get("/user/1234").
	Expect(t).
	Assert(jsonpath.Equal(`$.id`, "1234")).
	Assert(apitest.IsSuccess).
	End()

We should support a slice of assert functions and loop through them when performing the assertions.

Dependabot can't resolve your Go dependency files

Dependabot can't resolve your Go dependency files.

As a result, Dependabot couldn't update your dependencies.

The error Dependabot encountered was:


If you think the above is an error on Dependabot's side please don't hesitate to get in touch - we'll do whatever we can to fix it.

View the update logs.

Improvements to response headers assertion error messages

Is your feature request related to a problem? Please describe.
Hey! I was using apitest to assert some response headers and thought the messaging in case of errors could be improved a little and would like to get your opinions.
Taking this simple test as an example with an assertion on the response Location header

func TestApiTest_Redirect(t *testing.T) {
	apitest.New().
		Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			http.Redirect(w, r, "/foo", http.StatusSeeOther)
		})).
		Get("/").
		Expect(t).
		Status(http.StatusSeeOther).
		Header("Location", "/bar").
		End()
}

The test result shows

=== RUN   TestApiTest_Redirect
    TestApiTest_Redirect: /home/user/workspace/apitest/apitest.go:967: could not match header=Location
--- FAIL: TestApiTest_Redirect (0.00s)

The error message details could not match header=Location and the header is present in the response, but the value is incorrect (/bar vs /foo)

Now let's take an example where the header is not present at all

func TestApiTest_Redirect(t *testing.T) {
	apitest.New().
		Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			http.Redirect(w, r, "/foo", http.StatusSeeOther)
		})).
		Get("/").
		Expect(t).
		Status(http.StatusSeeOther).
		Header("My-Header", "example").
		End()
}

The test results show

=== RUN   TestApiTest_Redirect
    TestApiTest_Redirect: /home/user/workspace/apitest/apitest.go:967: could not match header=My-Header
--- FAIL: TestApiTest_Redirect (0.00s)

The error message details could not match header=My-Header is the same as the previous test, but this time the header is not present at all.

The above tests are quite simple, but if you think of cases with more complex http handlers there is no way from the error message to understand if the header is not present at all or if it's present but the value is incorrect.

I've noticed that Cookie() takes a different approach to assertion error messages like below

func TestApiTest_Redirect2(t *testing.T) {
	apitest.New().
		Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			http.SetCookie(w, &http.Cookie{
				Name:  "ABC",
				Value: "12345",
			})
			http.Redirect(w, r, "/newurl", http.StatusSeeOther)
		})).
		Get("/").
		Expect(t).
		Status(http.StatusSeeOther).
		Cookie("ABC", "AAA").
		Cookie("DDD", "AAA").
		End()
}

which gives a clear feedback of which cookie is missing or which values are incorrect

=== RUN   TestApiTest_Redirect2
    TestApiTest_Redirect2: /home/user/workspace/apitest/assert.go:29: 
        	Error Trace:	assert.go:29
        	            				apitest.go:927
        	            				apitest.go:750
        	            				apitest.go:556
        	            				apitest_test.go:1073
        	Error:      	Not equal: 
        	            	expected: 0
        	            	actual  : 1
        	Test:       	TestApiTest_Redirect2
        	Messages:   	Mismatched field Value. Expected AAA but received 12345
    TestApiTest_Redirect2: /home/user/workspace/apitest/assert.go:29: 
        	Error Trace:	assert.go:29
        	            				apitest.go:926
        	            				apitest.go:750
        	            				apitest.go:556
        	            				apitest_test.go:1073
        	Error:      	Not equal: 
        	            	expected: true
        	            	actual  : false
        	Test:       	TestApiTest_Redirect2
        	Messages:   	ExpectedCookie not found - DDD
--- FAIL: TestApiTest_Redirect2 (0.00s)

Describe the solution you'd like
I think it would be nice for the library to give better feedback when asserting response headers using Header() or Headers() similar to the one when Cookie() is used.

Something like

TestApiTest_Redirect: /home/user/workspace/apitest/assert.go:29: 
        	Error Trace:	assert.go:29
        	            				apitest.go:974
        	            				apitest.go:749
        	            				apitest.go:556
        	            				apitest_test.go:1062
        	Error:      	Not equal: 
        	            	expected: 0
        	            	actual  : 1
        	Test:       	TestApiTest_Redirect
        	Messages:   	Mismatched values for header Location. Expected /bar but received /foo
    TestApiTest_Redirect: /home/user/workspace/apitest/assert.go:29: 
        	Error Trace:	assert.go:29
        	            				apitest.go:972
        	            				apitest.go:749
        	            				apitest.go:556
        	            				apitest_test.go:1062
        	Error:      	Not equal: 
        	            	expected: true
        	            	actual  : false
        	Test:       	TestApiTest_Redirect
        	Messages:   	expected header My-Header not present in response

Describe alternatives you've considered
One could call HeaderPresent("Location") before calling Header(), but I think it's more convenient sometimes to just use Header()/Headers() particularly if you're asserting the value of the header.
And even in you were to use HeaderPresent("Location") beforehand you still wouldn't get the information of the mismatched values, ex. Expected /bar but received /foo

Additional context
Let me know if this something you think is worth improving. Happy to pick it up if needed!

How to set RemoteAddr?

Is your feature request related to a problem? Please describe.

I have a simple function which finds out which IP address has made a request. It checks first X-REAL-IP then X-FORWARDED-FOR then falls back to RemoteAddr. I know that's not accurate but, in lieu of anything better, it will have to do.

Describe the solution you'd like

I cannot seem to test the code that does RemoteAddr as I am unable to set it within the apitest.New()[…] chain.

I might have missed it in the documentation.

Describe alternatives you've considered

Not testing it is fine really (the code is hardly complex and not massively critical) but I am aiming for 100% coverage…

Additional context

Here is the get IP code, if that makes any difference?

func getIP(r *http.Request) (string, error) {

        // Get IP from the X-REAL-IP header
        ip := r.Header.Get("X-REAL-IP")
        netIP := net.ParseIP(ip)
        if netIP != nil {
                return ip, nil
        }

        // Get IP from X-FORWARDED-FOR header
        ips := r.Header.Get("X-FORWARDED-FOR")
        splitIps := strings.Split(ips, ",")
        for _, ip := range splitIps {
                netIP := net.ParseIP(ip)
                if netIP != nil {
                        return ip, nil
                }
        }

        // Get IP from RemoteAddr
        ip, _, err := net.SplitHostPort(r.RemoteAddr)
        if err != nil {
                return "", err
        }
        netIP = net.ParseIP(ip)
        if netIP != nil {
                return ip, nil
        }

        // Bad!
        return "", fmt.Errorf("No valid ip found")
}

Difficult to see why mocks failed to match

It is pretty common to spend time debugging mocks that failed to match. We should provide some debug output to assist the user. This should be disabled by default. The debug output should record each request and the results against the mock under test. It should be possible for the user to then identify which request attribute failed to match, e.g. the http method or request body.

How we best present the information requires some thought and discussion. Ideally we'd have a nice table of results but we don't want to add any external dependencies for this, so let's keep it simple for now.

Rename this repo to `apitest`

A really annoying thing, I know, but maybe something to consider early on before this is used by many people:

The package name is different to the repo name, which means when you run go imports in a project which has this lib as a dependency, you get really ugly imports at the top, where there is an alias.

You can't, unfortunately, just change the package name to be api-test either, as this will make a different alias, with an underscore: api_test "github.com/steinfletcher/api-test".

Really annoying from the go imports tool, but it's really commonly used, and this is the kind of thing that will annoy a load of complete pricks like me ha.

Examples

import (
	// Current ugly alias
	apitest "github.com/steinfletcher/api-test"

	// Ugly alias if package name is changed to match repo name "api_test"
	api_test "github.com/steinfletcher/api-test"

	// Beautiful package name with repo name change, and no alias
	"github.com/steinfletcher/apitest"
)

**Also see: ** See https://blog.golang.org/package-names

Add http mock support

This needs discussion. It'd be nice to have a simpler version of something like gock. We might even depend on gock and provide automatic setup and teardown down. If so it should be provided as an add-on like jsonpath. I would be in favour of a native approach but it's a lot more effort.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.