Reputation: 86925
I'm using spring-boot
with WebClient
, which is autowired as a bean.
Problem: when writing a junit
integration test, I have to use okhttp MockWebServer
. This mock always starts up on a random port, eg localhost:14321
.
Now my WebClient
of course has a fixed url that it sends the requests to. This url may be given by an application.properties
parameter like webclient.url=https://my.domain.com/
, so I could override that field in a junit
test. But only statically.
Question: how can I reset the WebClient
bean inside a @SpringBootTest
so that it sends the requests always to my mock server?
@Service
public class WebClientService {
public WebClientService(WebClient.Builder builder, @Value("${webclient.url}" String url) {
this.webClient = builder.baseUrl(url)...build();
}
public Response send() {
return webClient.body().exchange().bodyToMono();
}
}
@Service
public void CallingService {
@Autowired
private WebClientService service;
public void call() {
service.send();
}
}
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class MyWebTest {
@Autowired
private CallingService calling;
@Test
public void test() {
MockWebServer mockWebServer = new MockWebServer();
System.out.println("Current mock server url: " + mockWebServer.url("/").toString()); //this is random
mockWebServer.enqueue(new MockResponse()....);
//TODO how to make the mocked server url public to the WebClient?
calling.call();
}
}
As you see, I'm writing a full realworld junit integration test. The only problem is: how can I pass the MockWebServer
url and port to the WebClient
so that it automatically sends the requests to my mock??
Sidenote: I definitely need a random port in MockWebServer
here to not interfer with other running tests or applications. Thus have to stick to the random port, and find a way to pass it to the webclient (or dynamically override the application property).
Update: I came up with the following, which works. But maybe anyone knows how to make the mockserver field non-static?
@ContextConfiguration(initializers = RandomPortInitializer.class)
public abstract class AbstractITest {
@ClassRule
public static final MockWebServer mockWebServer = new MockWebServer();
public static class RandomPortInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(applicationContext,
"webclient.url=" + mockWebServer.url("/").toString());
}
}
}
Upvotes: 24
Views: 19911
Reputation: 131
I was searching for "How to set explicit port to MockWebServer to avoid default port number"
Above hint worked for me.. i.e. mockWebServer.start(port);
I used Kotlin so my code became like below:
var mockWebServer: MockWebServer = MockWebServer().apply {
Logger.verbose("In MockHttpApiClient: apply{}")
this.dispatcher = createDispatcher()
start(8080)
}
Some one may get hint from this.
Upvotes: 1
Reputation: 31
Here's a solution that's working from me. Spring Boot 2 & Spring-Webflux 5.3.30 using SpringBootTest
MyServiceClientTestConfig creates injected beans port and myServiceClient, where
As documented we should not re-use a MockWebServer instance between tests so create it before each test using the port we found free at test initialization and shut it down after each test.
I suppose there is a slim chance that since I find the port once when Spring is initializing the application context and there are short periods of time where it is not in use by MockWebServer, before the first call to init and between tests, that another process could take the port but the odds of that happening during the lifetime of the test seem incredibly small.
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
public class MyServiceClientTest {
private static final Logger LOG = LoggerFactory.getLogger(MyServiceClientTest.class);
private MockWebServer mockWebServer;
@Autowired
private MyServiceClient myServiceClient;
@Autowired
private Integer port;
@BeforeEach
void init() throws IOException {
assertNotNull(myServiceClient);
assertNotNull(port);
// MockWebServer not re-usable between tests
mockWebServer = new MockWebServer();
mockWebServer.start(port);
assertNotNull(mockWebServer);
assertEquals(port, mockWebServer.getPort());
}
@AfterEach
void cleanUp() throws IOException {
// MockWebServer not re-usable between tests
mockWebServer.shutdown();
}
@Test
public void placeholderTest1() {
LOG.info("Hello 1 {}", mockWebServer.getPort());
}
@Test
public void placeholderTest2() {
LOG.info("Hello 2 {}", mockWebServer.getPort());
}
@Configuration
static class MyServiceClientTestConfig {
@Bean
Integer getFreePort () {
// find a free port to use for this test
return TestSocketUtils.findAvailableTcpPort();
}
@Bean
MyServiceClient getMyServiceClient(Integer port) {
return new MyServiceClient(String.format("http://localhost:%d", port));
}
}
}
Upvotes: 3
Reputation: 742
This is how you can set a WebClient base URL with the URL of the MockWebServer.
The WebClient and MockWebServer are light enough to be recreated for each test.
@SpringBootTest
class Tests {
@Autowired
private WebClient.Builder webClientBuilder;
private MockWebServer mockWebServer;
private WebClient client;
@BeforeEach
void setUp() throws Exception {
mockWebServer = new MockWebServer();
mockWebServer.start();
// Set WebClinet base URL with the the mock web server URL
webClientBuilder.baseUrl(mockWebServer.url("").toString());
client = webClientBuilder.build();
}
@AfterEach
void tearDown() throws Exception {
mockWebServer.shutdown();
}
@Test
void bodyIsExpected() throws InterruptedException {
mockWebServer.enqueue(new MockResponse().setStatus("HTTP/1.1 200 OK").setBody("Hello World!")
.addHeader("Content-Type", "text/plain"));
ResponseEntity<String> response = client.get().retrieve().toEntity(String.class).block();
assertThat(response.getBody(), is("Hello World!"));
}
}
Upvotes: 1
Reputation: 5482
Since Spring Framework 5.2.5 (Spring Boot 2.x) you can use DynamicPropertySource
annotation which is quite handy.
Here is a complete example how you can use it with MockWebServer
to bind the correct port:
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
public abstract class AbstractIT {
static MockWebServer mockWebServer;
@DynamicPropertySource
static void properties(DynamicPropertyRegistry r) throws IOException {
r.add("some-service.url", () -> "http://localhost:" + mockWebServer.getPort());
}
@BeforeAll
static void beforeAll() throws IOException {
mockWebServer = new MockWebServer();
mockWebServer.start();
}
@AfterAll
static void afterAll() throws IOException {
mockWebServer.shutdown();
}
}
Upvotes: 23