Add a dateValue HeaderResultMatcher
HTTP headers such as "Expires", "Last-Modified" all use date strings like "Tue, 21 Jul 2015 10:00:00 GMT". Prior to this commit, there was no way to match those header values, besides formatting dates manually. This commit introduces a new HeaderResultMatcher to test those date headers using a long timestamp: ``` this.mockMvc.perform(get("/persons/1").header("If-Modified-Since", now)) .andExpect(status().isNotModified()) .andExpect(header().dateValue("Last-Modified", timestamp)); ``` Issue: SPR-13263
This commit is contained in:
parent
ed20b3771c
commit
cf2aed9d00
|
@ -24,6 +24,11 @@ import org.springframework.test.web.servlet.ResultMatcher;
|
||||||
import static org.hamcrest.MatcherAssert.*;
|
import static org.hamcrest.MatcherAssert.*;
|
||||||
import static org.springframework.test.util.AssertionErrors.*;
|
import static org.springframework.test.util.AssertionErrors.*;
|
||||||
|
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.TimeZone;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Factory for response header assertions.
|
* Factory for response header assertions.
|
||||||
* <p>An instance of this class is usually accessed via
|
* <p>An instance of this class is usually accessed via
|
||||||
|
@ -31,6 +36,7 @@ import static org.springframework.test.util.AssertionErrors.*;
|
||||||
*
|
*
|
||||||
* @author Rossen Stoyanchev
|
* @author Rossen Stoyanchev
|
||||||
* @author Sam Brannen
|
* @author Sam Brannen
|
||||||
|
* @author Brian Clozel
|
||||||
* @since 3.2
|
* @since 3.2
|
||||||
*/
|
*/
|
||||||
public class HeaderResultMatchers {
|
public class HeaderResultMatchers {
|
||||||
|
@ -96,4 +102,26 @@ public class HeaderResultMatchers {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assert the primary value of the named response header as a date String,
|
||||||
|
* using the preferred date format described in RFC 7231.
|
||||||
|
* <p>The {@link ResultMatcher} returned by this method throws an {@link AssertionError}
|
||||||
|
* if the response does not contain the specified header, or if the supplied
|
||||||
|
* {@code value} does not match the primary value.
|
||||||
|
*
|
||||||
|
* @see <a href="https://tools.ietf.org/html/rfc7231#section-7.1.1.1">Section 7.1.1.1 of RFC 7231</a>
|
||||||
|
* @since 4.2
|
||||||
|
*/
|
||||||
|
public ResultMatcher dateValue(final String name, final long value) {
|
||||||
|
return new ResultMatcher() {
|
||||||
|
@Override
|
||||||
|
public void match(MvcResult result) {
|
||||||
|
SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
|
||||||
|
format.setTimeZone(TimeZone.getTimeZone("GMT"));
|
||||||
|
assertTrue("Response does not contain header " + name, result.getResponse().containsHeader(name));
|
||||||
|
assertEquals("Response header " + name, format.format(new Date(value)), result.getResponse().getHeader(name));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,13 +19,13 @@ package org.springframework.test.web.servlet.samples.standalone.resultmatchers;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.test.web.Person;
|
import org.springframework.test.web.Person;
|
||||||
import org.springframework.test.web.servlet.MockMvc;
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
import org.springframework.test.web.servlet.ResultMatcher;
|
import org.springframework.test.web.servlet.ResultMatcher;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.ResponseBody;
|
|
||||||
import org.springframework.web.context.request.WebRequest;
|
import org.springframework.web.context.request.WebRequest;
|
||||||
|
|
||||||
import static org.hamcrest.CoreMatchers.*;
|
import static org.hamcrest.CoreMatchers.*;
|
||||||
|
@ -34,11 +34,17 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder
|
||||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
|
||||||
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.*;
|
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.*;
|
||||||
|
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.TimeZone;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Examples of expectations on response header values.
|
* Examples of expectations on response header values.
|
||||||
*
|
*
|
||||||
* @author Rossen Stoyanchev
|
* @author Rossen Stoyanchev
|
||||||
* @author Sam Brannen
|
* @author Sam Brannen
|
||||||
|
* @author Brian Clozel
|
||||||
*/
|
*/
|
||||||
public class HeaderAssertionTests {
|
public class HeaderAssertionTests {
|
||||||
|
|
||||||
|
@ -50,6 +56,12 @@ public class HeaderAssertionTests {
|
||||||
|
|
||||||
private final long currentTime = System.currentTimeMillis();
|
private final long currentTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
private String now;
|
||||||
|
|
||||||
|
private String oneMinuteAgo;
|
||||||
|
|
||||||
|
private String oneSecondLater;
|
||||||
|
|
||||||
private MockMvc mockMvc;
|
private MockMvc mockMvc;
|
||||||
|
|
||||||
private PersonController personController;
|
private PersonController personController;
|
||||||
|
@ -57,6 +69,11 @@ public class HeaderAssertionTests {
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setup() {
|
public void setup() {
|
||||||
|
SimpleDateFormat dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
|
||||||
|
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
|
||||||
|
this.now = dateFormat.format(new Date(currentTime));
|
||||||
|
this.oneMinuteAgo = dateFormat.format(new Date(currentTime - (1000 * 60)));
|
||||||
|
this.oneSecondLater = dateFormat.format(new Date(currentTime + 1000));
|
||||||
this.personController = new PersonController();
|
this.personController = new PersonController();
|
||||||
this.personController.setStubTimestamp(currentTime);
|
this.personController.setStubTimestamp(currentTime);
|
||||||
this.mockMvc = standaloneSetup(this.personController).build();
|
this.mockMvc = standaloneSetup(this.personController).build();
|
||||||
|
@ -64,32 +81,38 @@ public class HeaderAssertionTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void stringWithCorrectResponseHeaderValue() throws Exception {
|
public void stringWithCorrectResponseHeaderValue() throws Exception {
|
||||||
this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, currentTime - (1000 * 60)))//
|
this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, oneMinuteAgo))//
|
||||||
.andExpect(header().string(LAST_MODIFIED, String.valueOf(currentTime)));
|
.andExpect(header().string(LAST_MODIFIED, now));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void stringWithMatcherAndCorrectResponseHeaderValue() throws Exception {
|
public void stringWithMatcherAndCorrectResponseHeaderValue() throws Exception {
|
||||||
this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, currentTime - (1000 * 60)))//
|
this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, oneMinuteAgo))//
|
||||||
.andExpect(header().string(LAST_MODIFIED, equalTo(String.valueOf(currentTime))));
|
.andExpect(header().string(LAST_MODIFIED, equalTo(now)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void dateValueWithCorrectResponseHeaderValue() throws Exception {
|
||||||
|
this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, oneMinuteAgo))//
|
||||||
|
.andExpect(header().dateValue(LAST_MODIFIED, currentTime));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void longValueWithCorrectResponseHeaderValue() throws Exception {
|
public void longValueWithCorrectResponseHeaderValue() throws Exception {
|
||||||
this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, currentTime - (1000 * 60)))//
|
this.mockMvc.perform(get("/persons/1"))//
|
||||||
.andExpect(header().longValue(LAST_MODIFIED, currentTime));
|
.andExpect(header().longValue("X-Rate-Limiting", 42));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void stringWithMissingResponseHeader() throws Exception {
|
public void stringWithMissingResponseHeader() throws Exception {
|
||||||
this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, currentTime))//
|
this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, now))//
|
||||||
.andExpect(status().isNotModified())//
|
.andExpect(status().isNotModified())//
|
||||||
.andExpect(header().string("X-Custom-Header", (String) null));
|
.andExpect(header().string("X-Custom-Header", (String) null));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void stringWithMatcherAndMissingResponseHeader() throws Exception {
|
public void stringWithMatcherAndMissingResponseHeader() throws Exception {
|
||||||
this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, currentTime))//
|
this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, now))//
|
||||||
.andExpect(status().isNotModified())//
|
.andExpect(status().isNotModified())//
|
||||||
.andExpect(header().string("X-Custom-Header", nullValue()));
|
.andExpect(header().string("X-Custom-Header", nullValue()));
|
||||||
}
|
}
|
||||||
|
@ -97,7 +120,7 @@ public class HeaderAssertionTests {
|
||||||
@Test
|
@Test
|
||||||
public void longValueWithMissingResponseHeader() throws Exception {
|
public void longValueWithMissingResponseHeader() throws Exception {
|
||||||
try {
|
try {
|
||||||
this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, currentTime))//
|
this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, now))//
|
||||||
.andExpect(status().isNotModified())//
|
.andExpect(status().isNotModified())//
|
||||||
.andExpect(header().longValue("X-Custom-Header", 99L));
|
.andExpect(header().longValue("X-Custom-Header", 99L));
|
||||||
|
|
||||||
|
@ -129,26 +152,28 @@ public class HeaderAssertionTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void stringWithIncorrectResponseHeaderValue() throws Exception {
|
public void stringWithIncorrectResponseHeaderValue() throws Exception {
|
||||||
long unexpected = currentTime + 1000;
|
assertIncorrectResponseHeaderValue(header().string(LAST_MODIFIED, oneSecondLater), oneSecondLater);
|
||||||
assertIncorrectResponseHeaderValue(header().string(LAST_MODIFIED, String.valueOf(unexpected)), unexpected);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void stringWithMatcherAndIncorrectResponseHeaderValue() throws Exception {
|
public void stringWithMatcherAndIncorrectResponseHeaderValue() throws Exception {
|
||||||
long unexpected = currentTime + 1000;
|
assertIncorrectResponseHeaderValue(header().string(LAST_MODIFIED, equalTo(oneSecondLater)), oneSecondLater);
|
||||||
assertIncorrectResponseHeaderValue(header().string(LAST_MODIFIED, equalTo(String.valueOf(unexpected))),
|
|
||||||
unexpected);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void longValueWithIncorrectResponseHeaderValue() throws Exception {
|
public void dateValueWithIncorrectResponseHeaderValue() throws Exception {
|
||||||
long unexpected = currentTime + 1000;
|
long unexpected = currentTime + 1000;
|
||||||
assertIncorrectResponseHeaderValue(header().longValue(LAST_MODIFIED, unexpected), unexpected);
|
assertIncorrectResponseHeaderValue(header().dateValue(LAST_MODIFIED, unexpected), oneSecondLater);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertIncorrectResponseHeaderValue(ResultMatcher resultMatcher, long unexpected) throws Exception {
|
@Test(expected = AssertionError.class)
|
||||||
|
public void longValueWithIncorrectResponseHeaderValue() throws Exception {
|
||||||
|
this.mockMvc.perform(get("/persons/1")).andExpect(header().longValue("X-Rate-Limiting", 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertIncorrectResponseHeaderValue(ResultMatcher resultMatcher, String unexpected) throws Exception {
|
||||||
try {
|
try {
|
||||||
this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, currentTime - (1000 * 60)))//
|
this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, oneMinuteAgo))//
|
||||||
.andExpect(resultMatcher);
|
.andExpect(resultMatcher);
|
||||||
|
|
||||||
fail(EXPECTED_ASSERTION_ERROR_MSG);
|
fail(EXPECTED_ASSERTION_ERROR_MSG);
|
||||||
|
@ -162,8 +187,8 @@ public class HeaderAssertionTests {
|
||||||
// We don't use assertEquals() since we cannot control the formatting
|
// We don't use assertEquals() since we cannot control the formatting
|
||||||
// produced by JUnit or Hamcrest.
|
// produced by JUnit or Hamcrest.
|
||||||
assertMessageContains(e, "Response header " + LAST_MODIFIED);
|
assertMessageContains(e, "Response header " + LAST_MODIFIED);
|
||||||
assertMessageContains(e, String.valueOf(unexpected));
|
assertMessageContains(e, unexpected);
|
||||||
assertMessageContains(e, String.valueOf(currentTime));
|
assertMessageContains(e, now);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -186,12 +211,12 @@ public class HeaderAssertionTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequestMapping("/persons/{id}")
|
@RequestMapping("/persons/{id}")
|
||||||
@ResponseBody
|
public ResponseEntity<Person> showEntity(@PathVariable long id, WebRequest request) {
|
||||||
public Person showEntity(@PathVariable long id, WebRequest request) {
|
return ResponseEntity
|
||||||
if (request.checkNotModified(calculateLastModified(id))) {
|
.ok()
|
||||||
return null;
|
.lastModified(calculateLastModified(id))
|
||||||
}
|
.header("X-Rate-Limiting", "42")
|
||||||
return new Person("Jason");
|
.body(new Person("Jason"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private long calculateLastModified(long id) {
|
private long calculateLastModified(long id) {
|
||||||
|
|
Loading…
Reference in New Issue