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

RedirectTo matcher #341

Closed
mapreal19 opened this issue Mar 21, 2019 · 5 comments
Closed

RedirectTo matcher #341

mapreal19 opened this issue Mar 21, 2019 · 5 comments
Assignees

Comments

@mapreal19
Copy link

Inspired by: https://relishapp.com/rspec/rspec-rails/docs/matchers/redirect-to-matcher

Does it make sense to have this matcher? Currently in my tests:

Expect(response.Header().Get("Location")).To(Equal("/"))

But I was able to use a custom matcher so the code becomes simpler:

Expect(response).To(RedirectTo("/"))
@williammartin
Copy link
Sponsor Collaborator

Hi @mapreal19, can you show me what your custom matcher code looks like? It seems like we'd have to be quite opinionated on the type that we expect i.e. http.Response (or more specifically, things that match an interface with the method Header().

Are there other structures common in the Go ecosystem to represent responses? Do they have Header() methods that look the same?

@mapreal19
Copy link
Author

Sure:

package matcher

import (
	"fmt"
	"github.com/onsi/gomega/types"
	"net/http/httptest"
	"reflect"
)

type redirectToMatcher struct {
	expected interface{}
}

func RedirectTo(expected interface{}) types.GomegaMatcher {
	return &redirectToMatcher{
		expected: expected,
	}
}

func (matcher *redirectToMatcher) Match(actual interface{}) (success bool, err error) {
	response, ok := actual.(*httptest.ResponseRecorder)
	if !ok {
		return false, fmt.Errorf("RedirectTo matcher expects an http.Response")
	}

	location := getLocation(response)
	success = reflect.DeepEqual(location, matcher.expected)

	return success, nil
}

func (matcher *redirectToMatcher) FailureMessage(actual interface{}) (message string) {
	return fmt.Sprintf(
		"Expected response to redirect to %#v, recieved: %#v",
		matcher.expected,
		getLocation(actual.(*httptest.ResponseRecorder)),
	)
}

func (matcher *redirectToMatcher) NegatedFailureMessage(actual interface{}) (message string) {
	return fmt.Sprintf(
		"Expected response not to redirect to %#v, recieved: %#v",
		matcher.expected,
		getLocation(actual.(*httptest.ResponseRecorder)),
	)
}

func getLocation(response *httptest.ResponseRecorder) string {
	return response.Header().Get("Location")
}

I'm assuming response is a httptest.ResponseRecorder which I believe is the most common scenario when testing controllers/handlers?

@mapreal19
Copy link
Author

@williammartin what if for now we support only httptest.ResponseRecorder? If someone else needs a different type we can make the change later.

@ansd ansd self-assigned this Jan 12, 2020
@ansd
Copy link
Collaborator

ansd commented Feb 16, 2020

@mapreal19 thanks for opening the issue and sending the code.

I have a couple of comments:

  1. Inspired by your issue we created an HaveHTTPStatus matcher (see Add HaveHTTPStatus matcher #378).
  2. In your code example above, do you think it makes more sense to allow the matcher to be called on both *http.Response and *httptest.ResponseRecorder?
  3. I think, we should first call Result() on the *httptest.ResponseRecorder before inspecting the headers since the docs state

To test the headers that were written after a handler completes, use the Result method and see the returned Response value's Header.

  1. I'm not sure how much sense it makes to add only a RedirectTo matcher. (Looking at the Rspec Redirect_to matcher, it does way more than a simple string comparison.) When adding a RedirectTo matcher, why not adding matchers for other HTTP response headers such as HaveMIMEType similarly as done in

    gomega/ghttp/handlers.go

    Lines 61 to 67 in 6be6c43

    //VerifyMimeType returns a handler that verifies that a request has a specified mime type set
    //in Content-Type header
    func VerifyMimeType(mimeType string) http.HandlerFunc {
    return func(w http.ResponseWriter, req *http.Request) {
    Expect(strings.Split(req.Header.Get("Content-Type"), ";")[0]).Should(Equal(mimeType))
    }
    }

    I currently don't have a strong opinion whether to add multiple such HTTP response header matchers or none of them. @blgm what do you think?

@mapreal19
Copy link
Author

@ansd

For 2. maybe, in all my tests I only used a httptest.ResponseRecorder, so not sure if others will use a http.Response.

For me it was just convenient, it's a minimal implementation. My hope is that others will contribute with more complex edge cases.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants