diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/result/HeaderResultMatchers.java b/spring-test/src/main/java/org/springframework/test/web/servlet/result/HeaderResultMatchers.java index 058aa5cd5a..c57387ee14 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/result/HeaderResultMatchers.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/result/HeaderResultMatchers.java @@ -24,6 +24,11 @@ import org.springframework.test.web.servlet.ResultMatcher; import static org.hamcrest.MatcherAssert.*; 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. *

An instance of this class is usually accessed via @@ -31,6 +36,7 @@ import static org.springframework.test.util.AssertionErrors.*; * * @author Rossen Stoyanchev * @author Sam Brannen + * @author Brian Clozel * @since 3.2 */ 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. + *

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 Section 7.1.1.1 of RFC 7231 + * @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)); + } + }; + } + } diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/HeaderAssertionTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/HeaderAssertionTests.java index db13da05df..e81824c8fd 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/HeaderAssertionTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/HeaderAssertionTests.java @@ -19,13 +19,13 @@ package org.springframework.test.web.servlet.samples.standalone.resultmatchers; import org.junit.Before; import org.junit.Test; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.test.web.Person; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultMatcher; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.context.request.WebRequest; 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.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. * * @author Rossen Stoyanchev * @author Sam Brannen + * @author Brian Clozel */ public class HeaderAssertionTests { @@ -50,6 +56,12 @@ public class HeaderAssertionTests { private final long currentTime = System.currentTimeMillis(); + private String now; + + private String oneMinuteAgo; + + private String oneSecondLater; + private MockMvc mockMvc; private PersonController personController; @@ -57,6 +69,11 @@ public class HeaderAssertionTests { @Before 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.setStubTimestamp(currentTime); this.mockMvc = standaloneSetup(this.personController).build(); @@ -64,32 +81,38 @@ public class HeaderAssertionTests { @Test public void stringWithCorrectResponseHeaderValue() throws Exception { - this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, currentTime - (1000 * 60)))// - .andExpect(header().string(LAST_MODIFIED, String.valueOf(currentTime))); + this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, oneMinuteAgo))// + .andExpect(header().string(LAST_MODIFIED, now)); } @Test public void stringWithMatcherAndCorrectResponseHeaderValue() throws Exception { - this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, currentTime - (1000 * 60)))// - .andExpect(header().string(LAST_MODIFIED, equalTo(String.valueOf(currentTime)))); + this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, oneMinuteAgo))// + .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 public void longValueWithCorrectResponseHeaderValue() throws Exception { - this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, currentTime - (1000 * 60)))// - .andExpect(header().longValue(LAST_MODIFIED, currentTime)); + this.mockMvc.perform(get("/persons/1"))// + .andExpect(header().longValue("X-Rate-Limiting", 42)); } @Test 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(header().string("X-Custom-Header", (String) null)); } @Test 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(header().string("X-Custom-Header", nullValue())); } @@ -97,7 +120,7 @@ public class HeaderAssertionTests { @Test public void longValueWithMissingResponseHeader() throws Exception { 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(header().longValue("X-Custom-Header", 99L)); @@ -129,26 +152,28 @@ public class HeaderAssertionTests { @Test public void stringWithIncorrectResponseHeaderValue() throws Exception { - long unexpected = currentTime + 1000; - assertIncorrectResponseHeaderValue(header().string(LAST_MODIFIED, String.valueOf(unexpected)), unexpected); + assertIncorrectResponseHeaderValue(header().string(LAST_MODIFIED, oneSecondLater), oneSecondLater); } @Test public void stringWithMatcherAndIncorrectResponseHeaderValue() throws Exception { - long unexpected = currentTime + 1000; - assertIncorrectResponseHeaderValue(header().string(LAST_MODIFIED, equalTo(String.valueOf(unexpected))), - unexpected); + assertIncorrectResponseHeaderValue(header().string(LAST_MODIFIED, equalTo(oneSecondLater)), oneSecondLater); } @Test - public void longValueWithIncorrectResponseHeaderValue() throws Exception { + public void dateValueWithIncorrectResponseHeaderValue() throws Exception { 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 { - 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); fail(EXPECTED_ASSERTION_ERROR_MSG); @@ -162,8 +187,8 @@ public class HeaderAssertionTests { // We don't use assertEquals() since we cannot control the formatting // produced by JUnit or Hamcrest. assertMessageContains(e, "Response header " + LAST_MODIFIED); - assertMessageContains(e, String.valueOf(unexpected)); - assertMessageContains(e, String.valueOf(currentTime)); + assertMessageContains(e, unexpected); + assertMessageContains(e, now); } } @@ -186,12 +211,12 @@ public class HeaderAssertionTests { } @RequestMapping("/persons/{id}") - @ResponseBody - public Person showEntity(@PathVariable long id, WebRequest request) { - if (request.checkNotModified(calculateLastModified(id))) { - return null; - } - return new Person("Jason"); + public ResponseEntity showEntity(@PathVariable long id, WebRequest request) { + return ResponseEntity + .ok() + .lastModified(calculateLastModified(id)) + .header("X-Rate-Limiting", "42") + .body(new Person("Jason")); } private long calculateLastModified(long id) {