Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

assert: Add HTTP builder to combine request x response using builder. #1491

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

Matovidlo
Copy link

@Matovidlo Matovidlo commented Oct 23, 2023

Summary

Adds new method into assert http_assertions.go. Should be used as generic solution for testing request parameters to response possibilities using 1 method with options.
Previous solutions did not have response header check method. However implementing that it would be necessary to create single test with 3 assert conditions.
Now it is possible to use single function to test typical HTTP response values(status, body, headers).

Changes

Add new function HTTP(...) that can be customized based on user needs.
Use go generate ./... to update generated code.
Add new test TestHTTPBuilder without example usage.

Motivation

Impossible to test HTTP response headers.

assert.True(mockAssert.HTTP(httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, WithCode(200), WithResponseHeader(http.Header{"Content-Type": []string{"text/plain; charset=utf-8"}}), WithExpectedBody(*bytes.NewBufferString("Hello, World!"))))

Related issues

Closes #598

@dolmen dolmen added the pkg-assert Change related to package testify/assert label Oct 30, 2023
@arjunmahishi
Copy link
Collaborator

arjunmahishi commented Feb 15, 2024

Thanks for putting up this PR! 🎉

Honestly, I am not a fan of the assert package calling the handler function as a part of an assert function/method. IMO, this package should only match values. I would write HTTP assertions like this:

resp, err := makeSomeHTTPCall()
require.NoError(t, err)
defer resp.Body.Close()

assert.HTTPStatus(t, resp, http.StatusOK)
assert.HTTPRespBodyContains(t, resp, "foo")
assert.HTTPRespHeaderContains(t, resp, ...)

Simple assertions on a given value.

But it looks like there already are some functions (like HTTPBodyContains, HTTPStatusCode, etc) that take the handler as an argument and execute the HTTP request as a part of the assertion function.


Would love to hear the thoughts of other maintainers on this.
@dolmen @brackendawson

@Matovidlo
Copy link
Author

Matovidlo commented Feb 16, 2024

@arjunmahishi
Hello,
I just added new function to already created module in simillar way. I am not sure how to refactor it so the code base looks fine. Either it would need first of all refactor of the module and then make this PR as feature to new code base.

Also there would be a major change if we refactor these function to not work with handler. So when projects with already created tests would need to refactor it. Probablly this is for further discussion in some Issue and create for example v2 branch?

Just to mention the motivation, due to not accepting this PR I need 2 testing frameworks to test the HTTP request, response. The issue I found is that it is not possible to test the response header/headers. However, to simplify the tests I thought it would be better to create 1 assert function. The developer will scope the HTTP by what is the developer testing.

I would like to receive at least some comment whether I have to refactor it to have just HTTPResponseHeaderContains Instead of HTTP.

#601 here is simillar PR with header tests that could be compared with this PR.

Have a nice day

@arjunmahishi
Copy link
Collaborator

arjunmahishi commented Feb 16, 2024

Yes. I understand the intent of this PR. Maybe I did not phrase it correctly. Don't let my comment be a blocker for this PR. The testing strategy i mentioned would require a lot of refactoring. And its probably not worth doing that. I was just putting it out there.

I think the changes made in this PR are useful. Other maintainers who've been around for longer can give more contextual comments on this.

h.Helper()
}

b := &builder{}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the options is optional, should the builder have some defaults?

Consider this:

func handler(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("hello"))
}

func TestHttp(t *testing.T) {
	assert.HTTP(t, handler, http.MethodGet, "/", nil)
}

Fails with:

=== RUN   TestHttp
    main_test.go:15: 
        	Error Trace:	/Users/armahishi/dev/personal/forks/testify/example/main_test.go:15
        	Error:      	Expected HTTP success status code for "/?" but received 200
        	Test:       	TestHttp
--- FAIL: TestHttp (0.00s)
FAIL
FAIL	github.com/stretchr/testify/example	0.173s
FAIL

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point I have added the default code for builder.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have made a change, based on what you said in next review step.
There are no defaults as the option would override something that is default for the builder. You mentioned that you expected that the status.code is not tested as You did not ask for it.
As the options should be optional, I made a change when you select no option, there is nothing to be tested so the test fails. It is possible to to remove this check, however it is incorrectly tested handler as there is not assertion.

Comment on lines +179 to +189
for _, option := range options {
err := option(b)
if err != nil {
Fail(t, fmt.Sprintf("Failed to build http options, got error: %s", err))
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If only a subset of options are provided, it should only assert that.

Example:

func handler(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("hello"))
}

func TestHttp(t *testing.T) {
	buf := bytes.NewBufferString("hello")
	assert.HTTP(t, handler, http.MethodGet, "/", nil, assert.WithExpectedBody(*buf))
}
=== RUN   TestHttp
    main_test.go:18: 
        	Error Trace:	/Users/armahishi/dev/personal/forks/testify/example/main_test.go:18
        	Error:      	Expected HTTP success status code for "/?" but received 200
        	Test:       	TestHttp
--- FAIL: TestHttp (0.00s)
FAIL
FAIL	github.com/stretchr/testify/example	0.162s
FAIL

In general, if the all those options are not optional, they should not be variadic.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To preserve the variadic options, then the code cannot be set as default because there is option that would be set up always and therefore it is not an option, it would be a required parameter of function.
Therefore I decided to make a status.code to be option however to make sure the usage of tests is correct, I throw a fail when no options are set as there is nothing to assert using result of handler call.
Basically it is a prevention of missuse of test that it does nothing except the handler execution.

Comment on lines 197 to 205
if w.Code != b.code {
Fail(t, fmt.Sprintf("Expected HTTP success status code for %q but received %d", url+"?"+values.Encode(), w.Code))
return false
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can reuse the existing assertions. It already has a lot of diff logic.

Suggested change
if w.Code != b.code {
Fail(t, fmt.Sprintf("Expected HTTP success status code for %q but received %d", url+"?"+values.Encode(), w.Code))
return false
}
Equal(t, b.code, w.Code)
=== RUN   TestHttp
    main_test.go:15: 
        	Error Trace:	/Users/armahishi/dev/personal/forks/testify/example/main_test.go:15
        	Error:      	Expected HTTP success status code for "/?" but received 200
        	Test:       	TestHttp
--- FAIL: TestHttp (0.00s)
FAIL
FAIL	github.com/stretchr/testify/example	0.161s
FAIL

VS

=== RUN   TestHttp
    main_test.go:15: 
        	Error Trace:	/Users/armahishi/dev/personal/forks/testify/example/main_test.go:15
        	Error:      	Not equal: 
        	            	expected: 404
        	            	actual  : 200
        	Test:       	TestHttp
--- FAIL: TestHttp (0.00s)
FAIL
FAIL	github.com/stretchr/testify/example	0.163s
FAIL

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see your point, however I wanted to be concise with the code style in the module, and there is not usage of Equal even though it is possible.
As I am more sceptical to use module itself I think it is correct to not use Equal function as if it will be changed in future, you have to rework whole http_assertions.go module as it is dependend on that.
It is probably bad assumption but I think we can have a discussion about this and possibly make another PR with appropriate refactor of http_assertions.go module. 😅

Martin Vaško added 2 commits April 17, 2024 13:39
When no option provided there is nothing to test. Throw a Fail when no
options are selected to prevent missuse of new function.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
pkg-assert Change related to package testify/assert
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Assert for HTTP Headers
4 participants