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

Allow MockRest to match header/queryParam value list with one Matcher #29953

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -23,6 +23,7 @@
import javax.xml.xpath.XPathExpressionException;

import org.hamcrest.Matcher;
import org.hamcrest.Matchers;

import org.springframework.http.HttpMethod;
import org.springframework.http.client.ClientHttpRequest;
Expand Down Expand Up @@ -114,6 +115,11 @@ public static RequestMatcher requestTo(URI uri) {

/**
* Assert request query parameter values with the given Hamcrest matcher(s).
* <p> Note that if the queryParam value list is larger than the number of provided
* {@code matchers}, extra values are considered acceptable.
* See {@link #queryParam(String, Matcher)} for a variant that takes a {@code Matcher} over
* the whole list of values.
* @see #queryParam(String, Matcher)
*/
@SafeVarargs
public static RequestMatcher queryParam(String name, Matcher<? super String>... matchers) {
Expand All @@ -128,6 +134,11 @@ public static RequestMatcher queryParam(String name, Matcher<? super String>...

/**
* Assert request query parameter values.
* <p> Note that if the queryParam value list is larger than {@code expectedValues},
* extra values are considered acceptable.
* See {@link #queryParam(String, Matcher)} for a variant that takes a {@code Matcher} over
* the whole list of values.
* @see #queryParam(String, Matcher)
*/
public static RequestMatcher queryParam(String name, String... expectedValues) {
return request -> {
Expand All @@ -139,6 +150,28 @@ public static RequestMatcher queryParam(String name, String... expectedValues) {
};
}

/**
* Assert request query parameter, matching on the whole {@code List} of values.
* <p> This can be used to check that the list has at least one value matching a criteria ({@link Matchers#hasItem(Matcher)}),
* or that every value in the list matches a common criteria ({@link Matchers#everyItem(Matcher)}),
* or that each value in the list matches its corresponding dedicated criteria ({@link Matchers#contains(Matcher[])},
* and more.
* @param name the name of the queryParam to consider
* @param matcher the matcher to apply to the whole list of values for that header
* @since 6.0.5
*/
public static RequestMatcher queryParam(String name, Matcher<? super List<String>> matcher) {
return request -> {
MultiValueMap<String, String> params = getQueryParams(request);
List<String> paramValues = params.get(name);
if (paramValues == null) {
fail("No queryParam [" + name + "]");
}
assertThat("Request queryParam values for [" + name + "]", paramValues, matcher);
};
}


private static MultiValueMap<String, String> getQueryParams(ClientHttpRequest request) {
return UriComponentsBuilder.fromUri(request.getURI()).build().getQueryParams();
}
Expand All @@ -158,6 +191,11 @@ private static void assertValueCount(

/**
* Assert request header values with the given Hamcrest matcher(s).
* <p> Note that if the header's value list is larger than the number of provided
* {@code matchers}, extra values are considered acceptable.
* See {@link #header(String, Matcher)} for a variant that takes a {@code Matcher} over
* the whole list of values.
* @see #header(String, Matcher)
*/
@SafeVarargs
public static RequestMatcher header(String name, Matcher<? super String>... matchers) {
Expand All @@ -173,6 +211,11 @@ public static RequestMatcher header(String name, Matcher<? super String>... matc

/**
* Assert request header values.
* <p> Note that if the header's value list is larger than {@code expectedValues},
* extra values are considered acceptable.
* See {@link #header(String, Matcher)} for a variant that takes a {@code Matcher} over
* the whole list of values.
* @see #header(String, Matcher)
*/
public static RequestMatcher header(String name, String... expectedValues) {
return request -> {
Expand All @@ -185,6 +228,26 @@ public static RequestMatcher header(String name, String... expectedValues) {
};
}

/**
* Assert request header, matching on the whole {@code List} of values.
* <p> This can be used to check that the list has at least one value matching a criteria ({@link Matchers#hasItem(Matcher)}),
* or that every value in the list matches a common criteria ({@link Matchers#everyItem(Matcher)}),
* or that each value in the list matches its corresponding dedicated criteria ({@link Matchers#contains(Matcher[])},
* and more.
* @param name the name of the header to consider
* @param matcher the matcher to apply to the whole list of values for that header
* @since 6.0.5
*/
public static RequestMatcher header(String name, Matcher<? super List<String>> matcher) {
return request -> {
List<String> headerValues = request.getHeaders().get(name);
if (headerValues == null) {
fail("No header values for header [" + name + "]");
}
assertThat("Request header values for [" + name + "]", headerValues, matcher);
};
}

/**
* Assert that the given request header does not exist.
* @since 5.2
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -16,18 +16,35 @@

package org.springframework.test.web.client.match;

import java.io.IOException;
import java.net.URI;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import org.hamcrest.CoreMatchers;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Test;

import org.springframework.http.HttpMethod;
import org.springframework.mock.http.client.MockClientHttpRequest;

import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.any;
import static org.hamcrest.Matchers.anything;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.everyItem;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.Matchers.startsWith;

/**
* Unit tests for {@link MockRestRequestMatchers}.
Expand Down Expand Up @@ -146,6 +163,63 @@ void headerContainsWithMissingValue() {
.hasMessageContaining("was \"bar\"");
}

@Test
void headerListMissing() {
assertThatThrownBy(() -> MockRestRequestMatchers.header("foo", hasSize(2)).match(this.request))
.isInstanceOf(AssertionError.class)
.hasMessage("No header values for header [foo]");
}

@Test
void headerListMatchers() throws IOException {
this.request.getHeaders().put("foo", Arrays.asList("bar", "baz"));

MockRestRequestMatchers.header("foo", containsInAnyOrder(endsWith("baz"), endsWith("bar"))).match(this.request);
MockRestRequestMatchers.header("foo", contains(is("bar"), is("baz"))).match(this.request);
MockRestRequestMatchers.header("foo", contains(is("bar"), Matchers.anything())).match(this.request);
MockRestRequestMatchers.header("foo", hasItem(endsWith("baz"))).match(this.request);
MockRestRequestMatchers.header("foo", everyItem(startsWith("ba"))).match(this.request);
MockRestRequestMatchers.header("foo", hasSize(2)).match(this.request);

//these can be a bit ambiguous when reading the test (the compiler selects the list matcher):
MockRestRequestMatchers.header("foo", notNullValue()).match(this.request);
MockRestRequestMatchers.header("foo", is(anything())).match(this.request);
MockRestRequestMatchers.header("foo", allOf(notNullValue(), notNullValue())).match(this.request);

//these are not as ambiguous thanks to an inner matcher that is either obviously list-oriented,
//string-oriented or obviously a vararg of matchers
//list matcher version
MockRestRequestMatchers.header("foo", allOf(notNullValue(), hasSize(2))).match(this.request);
//vararg version
MockRestRequestMatchers.header("foo", allOf(notNullValue(), endsWith("ar"))).match(this.request);
MockRestRequestMatchers.header("foo", is((any(String.class)))).match(this.request);
MockRestRequestMatchers.header("foo", CoreMatchers.either(is("bar")).or(is(nullValue()))).match(this.request);
MockRestRequestMatchers.header("foo", is(notNullValue()), is(notNullValue())).match(this.request);
}

@Test
void headerListContainsMismatch() {
this.request.getHeaders().put("foo", Arrays.asList("bar", "baz"));

assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> MockRestRequestMatchers
.header("foo", contains(containsString("ba"))).match(this.request))
.withMessage("Request header values for [foo]\n"
+ "Expected: iterable containing [a string containing \"ba\"]\n"
+ " but: not matched: \"baz\"");

assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> MockRestRequestMatchers
.header("foo", hasItem(endsWith("ba"))).match(this.request))
.withMessage("Request header values for [foo]\n"
+ "Expected: a collection containing a string ending with \"ba\"\n"
+ " but: mismatches were: [was \"bar\", was \"baz\"]");

assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> MockRestRequestMatchers
.header("foo", everyItem(endsWith("ar"))).match(this.request))
.withMessage("Request header values for [foo]\n"
+ "Expected: every item is a string ending with \"ar\"\n"
+ " but: an item was \"baz\"");
}

@Test
void headers() throws Exception {
this.request.getHeaders().put("foo", Arrays.asList("bar", "baz"));
Expand Down Expand Up @@ -210,4 +284,62 @@ void queryParamContainsWithMissingValue() {
.hasMessageContaining("was \"bar\"");
}


@Test
void queryParamListMissing() {
assertThatThrownBy(() -> MockRestRequestMatchers.queryParam("foo", hasSize(2)).match(this.request))
.isInstanceOf(AssertionError.class)
.hasMessage("No queryParam [foo]");
}

@Test
void queryParamListMatchers() throws IOException {
this.request.setURI(URI.create("http://www.foo.example/a?foo=bar&foo=baz"));

MockRestRequestMatchers.queryParam("foo", containsInAnyOrder(endsWith("baz"), endsWith("bar"))).match(this.request);
MockRestRequestMatchers.queryParam("foo", contains(is("bar"), is("baz"))).match(this.request);
MockRestRequestMatchers.queryParam("foo", contains(is("bar"), Matchers.anything())).match(this.request);
MockRestRequestMatchers.queryParam("foo", hasItem(endsWith("baz"))).match(this.request);
MockRestRequestMatchers.queryParam("foo", everyItem(startsWith("ba"))).match(this.request);
MockRestRequestMatchers.queryParam("foo", hasSize(2)).match(this.request);

//these can be a bit ambiguous when reading the test (the compiler selects the list matcher):
MockRestRequestMatchers.queryParam("foo", notNullValue()).match(this.request);
MockRestRequestMatchers.queryParam("foo", is(anything())).match(this.request);
MockRestRequestMatchers.queryParam("foo", allOf(notNullValue(), notNullValue())).match(this.request);

//these are not as ambiguous thanks to an inner matcher that is either obviously list-oriented,
//string-oriented or obviously a vararg of matchers
//list matcher version
MockRestRequestMatchers.queryParam("foo", allOf(notNullValue(), hasSize(2))).match(this.request);
//vararg version
MockRestRequestMatchers.queryParam("foo", allOf(notNullValue(), endsWith("ar"))).match(this.request);
MockRestRequestMatchers.queryParam("foo", is((any(String.class)))).match(this.request);
MockRestRequestMatchers.queryParam("foo", CoreMatchers.either(is("bar")).or(is(nullValue()))).match(this.request);
MockRestRequestMatchers.queryParam("foo", is(notNullValue()), is(notNullValue())).match(this.request);
}

@Test
void queryParamListContainsMismatch() {
this.request.setURI(URI.create("http://www.foo.example/a?foo=bar&foo=baz"));

assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> MockRestRequestMatchers
.queryParam("foo", contains(containsString("ba"))).match(this.request))
.withMessage("Request queryParam values for [foo]\n"
+ "Expected: iterable containing [a string containing \"ba\"]\n"
+ " but: not matched: \"baz\"");

assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> MockRestRequestMatchers
.queryParam("foo", hasItem(endsWith("ba"))).match(this.request))
.withMessage("Request queryParam values for [foo]\n"
+ "Expected: a collection containing a string ending with \"ba\"\n"
+ " but: mismatches were: [was \"bar\", was \"baz\"]");

assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> MockRestRequestMatchers
.queryParam("foo", everyItem(endsWith("ar"))).match(this.request))
.withMessage("Request queryParam values for [foo]\n"
+ "Expected: every item is a string ending with \"ar\"\n"
+ " but: an item was \"baz\"");
}

}