Skip to content

Commit cf2aed9

Browse files
committed
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
1 parent ed20b37 commit cf2aed9

File tree

2 files changed

+80
-27
lines changed

2 files changed

+80
-27
lines changed

spring-test/src/main/java/org/springframework/test/web/servlet/result/HeaderResultMatchers.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,19 @@
2424
import static org.hamcrest.MatcherAssert.*;
2525
import static org.springframework.test.util.AssertionErrors.*;
2626

27+
import java.text.SimpleDateFormat;
28+
import java.util.Date;
29+
import java.util.Locale;
30+
import java.util.TimeZone;
31+
2732
/**
2833
* Factory for response header assertions.
2934
* <p>An instance of this class is usually accessed via
3035
* {@link MockMvcResultMatchers#header}.
3136
*
3237
* @author Rossen Stoyanchev
3338
* @author Sam Brannen
39+
* @author Brian Clozel
3440
* @since 3.2
3541
*/
3642
public class HeaderResultMatchers {
@@ -96,4 +102,26 @@ public void match(MvcResult result) {
96102
};
97103
}
98104

105+
/**
106+
* Assert the primary value of the named response header as a date String,
107+
* using the preferred date format described in RFC 7231.
108+
* <p>The {@link ResultMatcher} returned by this method throws an {@link AssertionError}
109+
* if the response does not contain the specified header, or if the supplied
110+
* {@code value} does not match the primary value.
111+
*
112+
* @see <a href="https://tools.ietf.org/html/rfc7231#section-7.1.1.1">Section 7.1.1.1 of RFC 7231</a>
113+
* @since 4.2
114+
*/
115+
public ResultMatcher dateValue(final String name, final long value) {
116+
return new ResultMatcher() {
117+
@Override
118+
public void match(MvcResult result) {
119+
SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
120+
format.setTimeZone(TimeZone.getTimeZone("GMT"));
121+
assertTrue("Response does not contain header " + name, result.getResponse().containsHeader(name));
122+
assertEquals("Response header " + name, format.format(new Date(value)), result.getResponse().getHeader(name));
123+
}
124+
};
125+
}
126+
99127
}

spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/HeaderAssertionTests.java

Lines changed: 52 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,13 @@
1919
import org.junit.Before;
2020
import org.junit.Test;
2121

22+
import org.springframework.http.ResponseEntity;
2223
import org.springframework.stereotype.Controller;
2324
import org.springframework.test.web.Person;
2425
import org.springframework.test.web.servlet.MockMvc;
2526
import org.springframework.test.web.servlet.ResultMatcher;
2627
import org.springframework.web.bind.annotation.PathVariable;
2728
import org.springframework.web.bind.annotation.RequestMapping;
28-
import org.springframework.web.bind.annotation.ResponseBody;
2929
import org.springframework.web.context.request.WebRequest;
3030

3131
import static org.hamcrest.CoreMatchers.*;
@@ -34,11 +34,17 @@
3434
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
3535
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.*;
3636

37+
import java.text.SimpleDateFormat;
38+
import java.util.Date;
39+
import java.util.Locale;
40+
import java.util.TimeZone;
41+
3742
/**
3843
* Examples of expectations on response header values.
3944
*
4045
* @author Rossen Stoyanchev
4146
* @author Sam Brannen
47+
* @author Brian Clozel
4248
*/
4349
public class HeaderAssertionTests {
4450

@@ -50,54 +56,71 @@ public class HeaderAssertionTests {
5056

5157
private final long currentTime = System.currentTimeMillis();
5258

59+
private String now;
60+
61+
private String oneMinuteAgo;
62+
63+
private String oneSecondLater;
64+
5365
private MockMvc mockMvc;
5466

5567
private PersonController personController;
5668

5769

5870
@Before
5971
public void setup() {
72+
SimpleDateFormat dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
73+
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
74+
this.now = dateFormat.format(new Date(currentTime));
75+
this.oneMinuteAgo = dateFormat.format(new Date(currentTime - (1000 * 60)));
76+
this.oneSecondLater = dateFormat.format(new Date(currentTime + 1000));
6077
this.personController = new PersonController();
6178
this.personController.setStubTimestamp(currentTime);
6279
this.mockMvc = standaloneSetup(this.personController).build();
6380
}
6481

6582
@Test
6683
public void stringWithCorrectResponseHeaderValue() throws Exception {
67-
this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, currentTime - (1000 * 60)))//
68-
.andExpect(header().string(LAST_MODIFIED, String.valueOf(currentTime)));
84+
this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, oneMinuteAgo))//
85+
.andExpect(header().string(LAST_MODIFIED, now));
6986
}
7087

7188
@Test
7289
public void stringWithMatcherAndCorrectResponseHeaderValue() throws Exception {
73-
this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, currentTime - (1000 * 60)))//
74-
.andExpect(header().string(LAST_MODIFIED, equalTo(String.valueOf(currentTime))));
90+
this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, oneMinuteAgo))//
91+
.andExpect(header().string(LAST_MODIFIED, equalTo(now)));
92+
}
93+
94+
@Test
95+
public void dateValueWithCorrectResponseHeaderValue() throws Exception {
96+
this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, oneMinuteAgo))//
97+
.andExpect(header().dateValue(LAST_MODIFIED, currentTime));
7598
}
7699

77100
@Test
78101
public void longValueWithCorrectResponseHeaderValue() throws Exception {
79-
this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, currentTime - (1000 * 60)))//
80-
.andExpect(header().longValue(LAST_MODIFIED, currentTime));
102+
this.mockMvc.perform(get("/persons/1"))//
103+
.andExpect(header().longValue("X-Rate-Limiting", 42));
81104
}
82105

83106
@Test
84107
public void stringWithMissingResponseHeader() throws Exception {
85-
this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, currentTime))//
108+
this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, now))//
86109
.andExpect(status().isNotModified())//
87110
.andExpect(header().string("X-Custom-Header", (String) null));
88111
}
89112

90113
@Test
91114
public void stringWithMatcherAndMissingResponseHeader() throws Exception {
92-
this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, currentTime))//
115+
this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, now))//
93116
.andExpect(status().isNotModified())//
94117
.andExpect(header().string("X-Custom-Header", nullValue()));
95118
}
96119

97120
@Test
98121
public void longValueWithMissingResponseHeader() throws Exception {
99122
try {
100-
this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, currentTime))//
123+
this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, now))//
101124
.andExpect(status().isNotModified())//
102125
.andExpect(header().longValue("X-Custom-Header", 99L));
103126

@@ -129,26 +152,28 @@ public void doesNotExistFail() throws Exception {
129152

130153
@Test
131154
public void stringWithIncorrectResponseHeaderValue() throws Exception {
132-
long unexpected = currentTime + 1000;
133-
assertIncorrectResponseHeaderValue(header().string(LAST_MODIFIED, String.valueOf(unexpected)), unexpected);
155+
assertIncorrectResponseHeaderValue(header().string(LAST_MODIFIED, oneSecondLater), oneSecondLater);
134156
}
135157

136158
@Test
137159
public void stringWithMatcherAndIncorrectResponseHeaderValue() throws Exception {
138-
long unexpected = currentTime + 1000;
139-
assertIncorrectResponseHeaderValue(header().string(LAST_MODIFIED, equalTo(String.valueOf(unexpected))),
140-
unexpected);
160+
assertIncorrectResponseHeaderValue(header().string(LAST_MODIFIED, equalTo(oneSecondLater)), oneSecondLater);
141161
}
142162

143163
@Test
144-
public void longValueWithIncorrectResponseHeaderValue() throws Exception {
164+
public void dateValueWithIncorrectResponseHeaderValue() throws Exception {
145165
long unexpected = currentTime + 1000;
146-
assertIncorrectResponseHeaderValue(header().longValue(LAST_MODIFIED, unexpected), unexpected);
166+
assertIncorrectResponseHeaderValue(header().dateValue(LAST_MODIFIED, unexpected), oneSecondLater);
147167
}
148168

149-
private void assertIncorrectResponseHeaderValue(ResultMatcher resultMatcher, long unexpected) throws Exception {
169+
@Test(expected = AssertionError.class)
170+
public void longValueWithIncorrectResponseHeaderValue() throws Exception {
171+
this.mockMvc.perform(get("/persons/1")).andExpect(header().longValue("X-Rate-Limiting", 1));
172+
}
173+
174+
private void assertIncorrectResponseHeaderValue(ResultMatcher resultMatcher, String unexpected) throws Exception {
150175
try {
151-
this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, currentTime - (1000 * 60)))//
176+
this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, oneMinuteAgo))//
152177
.andExpect(resultMatcher);
153178

154179
fail(EXPECTED_ASSERTION_ERROR_MSG);
@@ -162,8 +187,8 @@ private void assertIncorrectResponseHeaderValue(ResultMatcher resultMatcher, lon
162187
// We don't use assertEquals() since we cannot control the formatting
163188
// produced by JUnit or Hamcrest.
164189
assertMessageContains(e, "Response header " + LAST_MODIFIED);
165-
assertMessageContains(e, String.valueOf(unexpected));
166-
assertMessageContains(e, String.valueOf(currentTime));
190+
assertMessageContains(e, unexpected);
191+
assertMessageContains(e, now);
167192
}
168193
}
169194

@@ -186,12 +211,12 @@ public void setStubTimestamp(long timestamp) {
186211
}
187212

188213
@RequestMapping("/persons/{id}")
189-
@ResponseBody
190-
public Person showEntity(@PathVariable long id, WebRequest request) {
191-
if (request.checkNotModified(calculateLastModified(id))) {
192-
return null;
193-
}
194-
return new Person("Jason");
214+
public ResponseEntity<Person> showEntity(@PathVariable long id, WebRequest request) {
215+
return ResponseEntity
216+
.ok()
217+
.lastModified(calculateLastModified(id))
218+
.header("X-Rate-Limiting", "42")
219+
.body(new Person("Jason"));
195220
}
196221

197222
private long calculateLastModified(long id) {

0 commit comments

Comments
 (0)