Skip to content

Commit a688ac3

Browse files
committed
Improve testing of ReactiveWebServerApplicationContext
Closes gh-21314
1 parent 9657564 commit a688ac3

File tree

1 file changed

+189
-0
lines changed

1 file changed

+189
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
/*
2+
* Copyright 2012-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.web.reactive.context;
18+
19+
import java.util.ArrayDeque;
20+
import java.util.ArrayList;
21+
import java.util.Deque;
22+
import java.util.List;
23+
24+
import org.junit.jupiter.api.AfterEach;
25+
import org.junit.jupiter.api.Test;
26+
27+
import org.springframework.beans.factory.support.RootBeanDefinition;
28+
import org.springframework.boot.availability.AvailabilityChangeEvent;
29+
import org.springframework.boot.availability.ReadinessState;
30+
import org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer;
31+
import org.springframework.boot.web.reactive.server.MockReactiveWebServerFactory;
32+
import org.springframework.context.ApplicationContextException;
33+
import org.springframework.context.ApplicationEvent;
34+
import org.springframework.context.ApplicationListener;
35+
import org.springframework.context.event.ContextClosedEvent;
36+
import org.springframework.context.event.ContextRefreshedEvent;
37+
import org.springframework.core.env.ConfigurableEnvironment;
38+
import org.springframework.http.server.reactive.HttpHandler;
39+
40+
import static org.assertj.core.api.Assertions.assertThat;
41+
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
42+
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
43+
import static org.mockito.Mockito.verify;
44+
45+
/**
46+
* Tests for {@link ReactiveWebServerApplicationContext}.
47+
*
48+
* @author Andy Wilkinson
49+
*/
50+
class ReactiveWebServerApplicationContextTests {
51+
52+
private ReactiveWebServerApplicationContext context = new ReactiveWebServerApplicationContext();
53+
54+
@AfterEach
55+
void cleanUp() {
56+
this.context.close();
57+
}
58+
59+
@Test
60+
void whenThereIsNoWebServerFactoryBeanThenContextRefreshWillFail() {
61+
assertThatExceptionOfType(ApplicationContextException.class).isThrownBy(() -> this.context.refresh())
62+
.withMessageContaining(
63+
"Unable to start ReactiveWebApplicationContext due to missing ReactiveWebServerFactory bean");
64+
}
65+
66+
@Test
67+
void whenThereIsNoHttpHandlerBeanThenContextRefreshWillFail() {
68+
addWebServerFactoryBean();
69+
assertThatExceptionOfType(ApplicationContextException.class).isThrownBy(() -> this.context.refresh())
70+
.withMessageContaining("Unable to start ReactiveWebApplicationContext due to missing HttpHandler bean");
71+
}
72+
73+
@Test
74+
void whenThereAreMultipleWebServerFactoryBeansThenContextRefreshWillFail() {
75+
addWebServerFactoryBean();
76+
addWebServerFactoryBean("anotherWebServerFactory");
77+
assertThatExceptionOfType(ApplicationContextException.class).isThrownBy(() -> this.context.refresh())
78+
.withMessageContaining(
79+
"Unable to start ReactiveWebApplicationContext due to multiple ReactiveWebServerFactory beans");
80+
}
81+
82+
@Test
83+
void whenThereAreMultipleHttpHandlerBeansThenContextRefreshWillFail() {
84+
addWebServerFactoryBean();
85+
addHttpHandlerBean("httpHandler1");
86+
addHttpHandlerBean("httpHandler2");
87+
assertThatExceptionOfType(ApplicationContextException.class).isThrownBy(() -> this.context.refresh())
88+
.withMessageContaining(
89+
"Unable to start ReactiveWebApplicationContext due to multiple HttpHandler beans");
90+
}
91+
92+
@Test
93+
void whenContextIsRefreshedThenReactiveWebServerInitializedEventIsPublished() {
94+
addWebServerFactoryBean();
95+
addHttpHandlerBean();
96+
TestApplicationListener listener = new TestApplicationListener();
97+
this.context.addApplicationListener(listener);
98+
this.context.refresh();
99+
List<ApplicationEvent> events = listener.receivedEvents();
100+
assertThat(events).hasSize(2).extracting("class").containsExactly(ContextRefreshedEvent.class,
101+
ReactiveWebServerInitializedEvent.class);
102+
ReactiveWebServerInitializedEvent initializedEvent = (ReactiveWebServerInitializedEvent) events.get(1);
103+
assertThat(initializedEvent.getSource().getPort()).isGreaterThanOrEqualTo(0);
104+
assertThat(initializedEvent.getApplicationContext()).isEqualTo(this.context);
105+
}
106+
107+
@Test
108+
void whenContextIsRefreshedThenLocalServerPortIsAvailableFromTheEnvironment() {
109+
addWebServerFactoryBean();
110+
addHttpHandlerBean();
111+
new ServerPortInfoApplicationContextInitializer().initialize(this.context);
112+
this.context.refresh();
113+
ConfigurableEnvironment environment = this.context.getEnvironment();
114+
assertThat(environment.containsProperty("local.server.port")).isTrue();
115+
assertThat(environment.getProperty("local.server.port")).isEqualTo("8080");
116+
}
117+
118+
@Test
119+
void whenContextIsClosedThenWebServerIsStopped() {
120+
addWebServerFactoryBean();
121+
addHttpHandlerBean();
122+
this.context.refresh();
123+
MockReactiveWebServerFactory factory = this.context.getBean(MockReactiveWebServerFactory.class);
124+
this.context.close();
125+
verify(factory.getWebServer()).stop();
126+
}
127+
128+
@Test
129+
@SuppressWarnings("unchecked")
130+
void whenContextIsClosedThenApplicationAvailabilityChangesToRefusingTraffic() {
131+
addWebServerFactoryBean();
132+
addHttpHandlerBean();
133+
TestApplicationListener listener = new TestApplicationListener();
134+
this.context.refresh();
135+
this.context.addApplicationListener(listener);
136+
this.context.close();
137+
List<ApplicationEvent> events = listener.receivedEvents();
138+
assertThat(events).hasSize(2).extracting("class").contains(AvailabilityChangeEvent.class,
139+
ContextClosedEvent.class);
140+
assertThat(((AvailabilityChangeEvent<ReadinessState>) events.get(0)).getState())
141+
.isEqualTo(ReadinessState.REFUSING_TRAFFIC);
142+
}
143+
144+
@Test
145+
void whenTheContextIsRefreshedThenASubsequentRefreshAttemptWillFail() {
146+
addWebServerFactoryBean();
147+
addHttpHandlerBean();
148+
this.context.refresh();
149+
assertThatIllegalStateException().isThrownBy(() -> this.context.refresh())
150+
.withMessageContaining("multiple refresh attempts");
151+
}
152+
153+
private void addHttpHandlerBean() {
154+
addHttpHandlerBean("httpHandler");
155+
}
156+
157+
private void addHttpHandlerBean(String beanName) {
158+
this.context.registerBeanDefinition(beanName,
159+
new RootBeanDefinition(HttpHandler.class, () -> (request, response) -> null));
160+
}
161+
162+
private void addWebServerFactoryBean() {
163+
addWebServerFactoryBean("webServerFactory");
164+
}
165+
166+
private void addWebServerFactoryBean(String beanName) {
167+
this.context.registerBeanDefinition(beanName, new RootBeanDefinition(MockReactiveWebServerFactory.class));
168+
}
169+
170+
static class TestApplicationListener implements ApplicationListener<ApplicationEvent> {
171+
172+
private Deque<ApplicationEvent> events = new ArrayDeque<>();
173+
174+
@Override
175+
public void onApplicationEvent(ApplicationEvent event) {
176+
this.events.add(event);
177+
}
178+
179+
List<ApplicationEvent> receivedEvents() {
180+
List<ApplicationEvent> receivedEvents = new ArrayList<>();
181+
while (!this.events.isEmpty()) {
182+
receivedEvents.add(this.events.pollFirst());
183+
}
184+
return receivedEvents;
185+
}
186+
187+
}
188+
189+
}

0 commit comments

Comments
 (0)