Set property with wiremock random port in spring boot test - java

I have a Spring Boot test that uses wiremock to mock an external service. In order to avoid conflicts with parallel builds I don't want to set a fixed port number for wiremock and would like to rely on its dynamic port configuration.
The application uses a property (external.baseUrl) set in the application.yml (under src/test/resources). However I didn't find a way to programmatically override that. I've tried something like this:
WireMockServer wireMockServer = new WireMockServer();
wireMockServer.start();
WireMock mockClient = new WireMock("localhost", wireMockServer.port());
System.setProperty("external.baseUrl", "http://localhost:" + wireMockServer.port());
but it didn't work and the value in application.yml was used instead. All other solutions that I've looked at override the property with a static value (for example in some annotation), but I don't know the value of the wiremock port until the test is run.
Clarification:
Both spring boot and wiremock run on random ports. That's fine and I know how to get the value of both ports. However wiremock is supposed to mock an external service and I need to tell my application how to reach it. I do this with the external.baseUrl property. The value I want to set in my test depends of course on the wiremock port number. My problem is simply how to programmatically set a property in a spring boot test.

The property name mentioned in https://stackoverflow.com/a/48859553/309683 (i.e. wiremock.port) is not correct, at least since Spring Cloud Contract version 2.1.2.RELEASE.
1. Working example
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.contract.wiremock.AutoConfigureWireMock;
import org.springframework.core.env.Environment;
import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = RANDOM_PORT)
#AutoConfigureWireMock(port = 0)
public class PortServiceTest {
#Autowired
private Environment environment;
#Test
public void shouldPopulateEnvironmentWithWiremockPort() {
assertThat(environment.containsProperty("wiremock.server.port")).isTrue();
assertThat(environment.getProperty("wiremock.server.port")).matches("\\d+");
}
}
2. Other WireMock properties
Other than wiremock.server.port, #AutoConfigureWireMock populates the environment with some other properties too:
wiremock.server.https-port
wiremock.server.stubs[]
wiremock.server.files[]
3. Gradle dependencies
To use Spring Cloud Contract WireMock in a Gradle based project, add the following dependency to your project:
testImplementation 'org.springframework.cloud:spring-cloud-contract-wiremock:${version}'
4. Using in application.yaml files
If you configure your test application.yaml file like this:
sample:
port: ${wiremock.server.port}
And define the following beans:
#Component
#ConfigurationProperties(prefix = "sample")
#Data
public class PortProperties {
private Integer port;
}
#Service
#RequiredArgsConstructor(onConstructor = #__(#Autowired))
public class PortService {
private final PortProperties config;
public Integer getPort() {
return config.getPort();
}
}
You can verify that sample.port is set to the randomly chosen wiremock port:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = RANDOM_PORT)
#AutoConfigureWireMock(port = 0)
public class PortServiceTest {
#Autowired
private Environment environment;
#Autowired
private PortService portService;
#Test
public void shouldReturnWireMockPort() {
assertThat(portService.getPort())
.isNotNull()
.isEqualTo(Integer.parseInt(environment.getProperty("wiremock.server.port")));
}
}

Use property substitution in your application.properties:
external.baseUrl=http://exampleUrl:${wiremock.server.port}
This requires the wiremock.server.port property to be set before the SpringBootTest is initialised, which can be achieved by adding the #AutoConfigureWireMock annotation to your test class.

Consider using Spring Cloud Contract Wiremock
There is already a JUnit Rule builder which allows to specify ${wiremock.port} to set random port in property/yaml files
Or you can use WireMockRestServiceServer to bind WireMock to your RestTemplate so you don't even need to override URLs in your tests.

I could not find a way to override properties in a Spring Boot integration test, since the test is run only after the application is created and all the beans already configured.
As a work around I added a #TestConfiguration to the test to replace the beans in the application:
private static WireMockServer wireMockServer1 = getWireMockServer();
private static WireMockServer wireMockServer2 = getWireMockServer();
private static WireMockServer wireMockServer3 = getWireMockServer();
private static WireMockServer getWireMockServer() {
final WireMockServer wireMockServer = new WireMockServer(options().dynamicPort());
wireMockServer.start();
return wireMockServer;
}
#TestConfiguration
static class TestConfig {
#Bean
#Primary
public BeanUsingAProperty1 getBean1() {
BeanUsingAProperty myBean = new BeanUsingAProperty();
myBean.setPort(wireMockServer.port());
return myBean;
}
#Bean
#Primary
public BeanUsingAProperty2 getBean2() {
String baseUrl = "http://localhost:" + wireMockServer2.port();
return new BeanUsingAProperty2(baseUrl);
}
#Bean
#Primary
public BeanUsingAProperty3 getBean3() {
String baseUrl = "http://localhost:" + wireMockServer3.port() + "/request";
return new BeanUsingAProperty3(new RestTemplate(), baseUrl, "someOtherParameter");
}
}
This effectively replaced the BeanUsingAProperty with the one defined in the test so that it has the correct port number for Wiremock.
For this configuration to be picked up I had to add this class in the test annotation
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = {
MySpringBootApplication.class, MyIntegrationTest.TestConfig.class })
Note that I use the non-static Wiremock API, since I have several such external services that each need to be mocked. Note that how the different beans are built is different depending on how each was designed.

The approach I use to programmatically change a property when starting a Spring Boot app, is to pass the custom value into the application main entry-point String[] args. This will have the effect of over-riding all other means such as System properties, YML or other config files.
Here is an example:
String[] args = new String[]{"--my.prop=foo"};
SpringApplication.run(Application.class, args);
It will be easy for you to expose a static method or custom API which starts the Spring Boot app (for testing) and with the value you want.
And then, once you have the value of the wiremock port - things are easy. Here is an example: PaymentServiceContractTest.java
P.S. Karate (the open-source test examples I am using above) is a new alternative to WireMock, do check it out ;)

How are you reading external.baseUrl?
If you are using a #Value annotated property, you can use ReflectionTestUtils to set the port after you have setup the mock server.
ReflectionTestUtils.setField(yourTestClass, "youPort", wireMockServer.port());

When you use org.springframework.cloud:spring-cloud-contract-wiremock dependency, if you are a fan of annotation, you can just add #AutoConfigureWireMock(port = Options.DYNAMIC_PORT)

Related

Resolving port already in use in a Spring boot test DEFINED PORT

I have a spring boot application that fires up and executes a class that listens to Application Ready event to call an external service to fetch some data and then use that data to push some rules to the classpath for execution. For local testing we have mocked the external service within our application which works fine during the application startup.
The issue is while testing the application by running it with spring boot test annotation and embedded jetty container either on :
RANDOM PORT
DEFINED PORT
In case of RANDOM PORT, at the application startup, it picks up the url for the mock service from the properties file at a defined port and has no clue where the embedded container is running since it is randomly picked up, hence failing to give response.
In case of DEFINED PORT, for the first test case file it runs successfully, but the moment next file is picked up, it fails saying the port is already in use.
The test cases are partitioned logically in multiple files and need
the external service to be called before the container starts to load
the rules.
How can I either share the embedded container between test files in case of using defined port or refactor my application code instead to get hold of the random port while starting up during the test case execution.
Any help would be appreciated.
Application Startup code :
#Component
public class ApplicationStartup implements ApplicationListener<ApplicationReadyEvent> {
#Autowired
private SomeService someService;
#Override
public void onApplicationEvent(ApplicationReadyEvent arg0) {
try {
someService.callExternalServiceAndLoadData();
}
catch (Execption e) {}
}
}
Test Code Annotations: Test1
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
#TestPropertySource("classpath:test-application.properties")
public class Test1 {
#Autowired
private TestRestTemplate restTemplate;
#Test
public void tc1() throws IOException {.....}
Test Code Annotations: Test2
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
#TestPropertySource("classpath:test-application.properties")
public class Test2 {
#Autowired
private TestRestTemplate restTemplate;
#Test
public void tc1() throws IOException {.....}
If you insist on using the same port on multiple test, you can prevent spring from caching the context for further tests by annotating your testclass with: #DirtiesContext
In your case:
#RunWith(SpringRunner.class)
#DirtiesContext
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
#TestPropertySource("classpath:test-application.properties")
Here is a quote from Andy Wilkinson from his answer on this discussion
This is working as designed. Spring Framework's test framework will, by default, cache contexts for possible reuse by multiple test classes. You have two tests with different configuration (due to #TestPropertySource) so they will use different application contexts. The context for the first test will be cached and kept open while the second test is running. Both tests are configured to use the same port for Tomcat's connector. As a result, when the second test is run, the context fails to start due to a port clash with the connector from the first test. You have a few options:
Use RANDOM_PORT
Remove #TestPropertySource from Test2 so that the contexts have identical configuration and the context from the first test can be reused for the second test.
Use #DirtiesContext so that the context isn't cached
I ran across the same issue. I know this question is a little old, but this may be of assistance:
Tests that use #SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT) can also inject the actual port into a field by using the #LocalServerPort annotation, as shown in the following example:
Source: https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#howto-user-a-random-unassigned-http-port
The code example given is:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT)
public class MyWebIntegrationTests {
#Autowired
ServletWebServerApplicationContext server;
#LocalServerPort
int port;
// ...
}
in application.properties
server.port=0
will run the application in random ports

Unit testing of Spring Boot Actuator endpoints not working when specifying a port

recently I changed my spring boot properties to define a management port.
In doing so, my unit tests started to fail :(
I wrote a unit test that tested the /metrics endpoint as follows:
#RunWith (SpringRunner.class)
#DirtiesContext
#SpringBootTest
public class MetricsTest {
#Autowired
private WebApplicationContext context;
private MockMvc mvc;
/**
* Called before each test.
*/
#Before
public void setUp() {
this.context.getBean(MetricsEndpoint.class).setEnabled(true);
this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build();
}
/**
* Test for home page.
*
* #throws Exception On failure.
*/
#Test
public void home()
throws Exception {
this.mvc.perform(MockMvcRequestBuilders.get("/metrics"))
.andExpect(MockMvcResultMatchers.status().isOk());
}
}
Previously this was passing. After adding:
management.port=9001
The tests started failing with:
home Failed: java.lang.AssertionError: Status expected: <200> but was: <404>
I tried changing the #SpringBootTest annotation with:
#SpringBootTest (properties = {"management.port=<server.port>"})
Where is the number used for the server.port. This didn't seem to make any difference.
So then changed the management.port value in the property file to be the same as the server.port. Same result.
The only way to get the test to work is remove the management.port from the property file.
Any suggestions/thoughts ?
Thanks
For Spring Boot 2.x the integration tests configuration could be simplified.
For example simple custom heartbeat endpoint
#Component
#Endpoint(id = "heartbeat")
public class HeartbeatEndpoint {
#ReadOperation
public String heartbeat() {
return "";
}
}
Where integration test for this endpoint
#SpringBootTest(
classes = HeartbeatEndpointTest.Config.class,
properties = {
"management.endpoint.heartbeat.enabled=true",
"management.endpoints.web.exposure.include=heartbeat"
})
#AutoConfigureMockMvc
#EnableAutoConfiguration
class HeartbeatEndpointTest {
private static final String ENDPOINT_PATH = "/actuator/heartbeat";
#Autowired
private MockMvc mockMvc;
#Test
void testHeartbeat() throws Exception {
mockMvc
.perform(get(ENDPOINT_PATH))
.andExpect(status().isOk())
.andExpect(content().string(""));
}
#Configuration
#Import(ProcessorTestConfig.class)
static class Config {
#Bean
public HeartbeatEndpoint heartbeatEndpoint() {
return new HeartbeatEndpoint();
}
}
}
For Spring boot test we need to specify the port it needs to connect to.
By default, it connects to server.port which in case of actuators is different.
This can be done by
#SpringBootTest(properties = "server.port=8090")
in application.properties we specify the management port as below
...
management.server.port=8090
...
Did you try adding the following annotation to your test class?
#TestPropertySource(properties = {"management.port=0"})
Check the following link for reference.
Isn't there an error in the property name?
Shouldn't be
#TestPropertySource(properties = {"management.server.port=..."}) instead of #TestPropertySource(properties = {"management.port=.."})
The guide stated that this can be achieved with #AutoConfigureMetrics.
And I moved with this.
Regardless of your classpath, meter registries, except the in-memory backed, are not auto-configured when using #SpringBootTest.
If you need to export metrics to a different backend as part of an integration test, annotate it with #AutoConfigureMetrics.
https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.testing.spring-boot-applications.metrics
Had the same issue, you just have to make the management.port null by adding this in your application-test.properties (set it to empty value)
management.port=
Make sure you use the test profile in your JUnit by annotating the class with
#ActiveProfiles("test")
Try using
#SpringBootTest(properties = {"management.port="})
Properties defined in the #SpringBootTest annotation have a higher precedence than those in application properties. "management.port=" will "unset" the management.port property.
This way you don't have to worry about configuring the port in your tests.
I was facing the same issue and tried several things but this is how I was able to solve mine without making any change in the application.yaml
Sample actuator endpoint
#Component
#RestControllerEndpoint(id = "endpoint")
public class SampleEndpoint
{
#GetMapping
public String sampleEndpoint(){
return ""
}
}
Unit test case
#RunWith(SpringRunner.class)
#SpringBootTest(
classes = {SampleEndpointTest.Config.class},
properties = {"management.server.port="}
)
#AutoConfigureMockMvc
public class SampleEndpointTest
{
#Autowired
private MockMvc mockMvc;
#SpringBootApplication(scanBasePackageClasses = {SampleEndpoint.class})
public static class Config
{
}
#Test
public void testSampleEndpoint() throws Exception
{
mockMvc.perform(
MockMvcRequestBuilders.get("/actuator/enpoint").accept(APPLICATION_JSON)
).andExpect(status().isOk());
}
Since now info endpoint must be enabled manually make sure the SpringBootTest tag includes this in properties, like this:
#SpringBootTest(
properties = {
"management.info.env.enabled=true" ,
"management.endpoints.web.exposure.include=info, health"
})
I had this problem recently, and as none of the above answers made any sense to me, I decided to do a bit more reading. In my case, I had already defined both server.port and management.server.port as 8091 in my test application-test.yaml file, and could not understand why my test was getting a connection refused error message.
It turns out that instead of using the annotation #SpringBootTest() I needed to use #SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) - which causes the port numbers in the yaml file to be used. This is briefly discussed in the manual. Quoting the relevant section:
DEFINED_PORT — Loads an EmbeddedWebApplicationContext and provides a real servlet environment. Embedded servlet containers are started and listening on a defined port (i.e from your application.properties or on the default port 8080).
It seems in SpringBootTest the default is to avoid starting a real servlet environment, and if no WebEnvironment is explicitly specified then SpringBootTest.WebEnvironment.MOCK is used as a default.
After a long search: There is this nice Springboot annotation called #LocalManagementPort!
It works similar to #LocalServerPort but for actuator endpoins.
An example config would look as follows
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class MetricsIT {
#Autowired
RestTemplateBuilder restTemplateBuilder;
#LocalManagementPort
int managementPort;
#Test
public void testMetrics(){
ResponseEntity<String> response = restTemplateBuilder
.rootUri("http://localhost:" + managementPort + "/actuator")
.build().exchange("/metrics", HttpMethod.GET, new HttpEntity<>(null), String.class);
}
}

Turn off embedded Elasticsearch in Spring Boot test

By default, Spring Boot will create an embedded Elasticsearch. It can be turned off by setting spring.data.elasticsearch.cluster-nodes. However, I'm not sure how to do this in a JUnit test. For example, I have:
#Slf4j
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(RemoteElasticsearch.class)
#SpringBootApplication(
scanBasePackageClasses = {
}
)
#EnableElasticsearchRepositories(basePackages = "com.example.me.repo")
public class RemoteElasticsearch {
#Inject
private SomeRepo someRepo;
#Test
public void test(){
someRepo.save(new Something());
}
}
It connects to the remote elasticsearch if I set the appropriate environment variable (eg spring.data.elasticsearch.cluster-node=host:9300). Can I somehow set this value directly on this test?
Just create second application.properties file in src/test/resources with spring.data.elasticsearch.cluster-nodes disabled. Spring Boot will use this file instead PROD configuration from src/main/resources.

When running test class, properties can't be read properly from .properties file by using #Value annotation, in Spring Maven project

//Update
After viewing helpful comments, I realize the problem should then be, how to unit test method using values read from properties by #Value .
//
I am working on this issue for days, I am writing unit test for a serviceClass.The serviceClass is like below :
import ...
#Component
public class ServiceClass implements ServiceInterface {
#Value("${data.layer.url}")
private String dataLayerUrl;
#Autowired
private RestTemplate restTemplate
public void dummy(){
restTemplate.postForObject(dataLayerUrl + "/" + ... , ...);
}
}
And CONFIG_DIR is already defined in application configuration file.
I have a SomeConfig class defining beans as below. (...src/main/java/com.app/configuration/SomeConfig)
#Configuration
#ComponentScan(basePackages = {"..."})
#PropertySource(value = "file:${CONFIG_DIR}/app.properties")
public class SomeConfig{
#Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
...
return restTemplate;
}
}
My test class is as below:
Import ...
#Profile("test")
public class ServiceClassTest extends AbstractTest {
#Value("${data.layer.url}")
private String dataLayerUrl;
#InjectMocks
private ServiceClass ServiceClass;
#Mock
RestTemplate restTemplate;
#Before
public void initializeMockito() {
MockitoAnnotations.initMocks(this);
}
#Test
public void dummyTest(){
when(restTemplate.postForObject(dataLayerUrl + "/" + ..., ...)).thenReturn(...);
serviceClass.dummy();
assertEquals(...);
verify(restTemplate).postForObject(...);
}
}
And then my AbstractTest as below :
#RunWith(SpringJUnit4ClassRunner.class)
#ActiveProfiles("test")
#SpringApplicationConfiguration(classes = Application.class)
#ContextConfiguration(classes = {TestConfiguration.class})
#ComponentScan(basePackages = ...)
public abstract class AbstractTest {
protected Logger logger = LoggerFactory.getLogger(this.getClass());
}
And I also have .../src/test/resources/application-test.properties defined as below
#Datalayer properties
data.layer.url=http://camel-dev-01.xxx.com:5001
This is the same as defined in application.properties(which locates outside of project in CONFIG_DIR.
The logic of testing is just to make sure when you call dummy method of serviceClass, the postForObject method of restTemplate is called exactly once.
But when doing it this way, I am facing with 2 problems.
when I run test class in debug mode, I found
in ServiceClassTest. dataLayerUrl = "$data.layer.url"
in ServiceClass. dataLayerUrl = null
I researched around and be able to solve problem one by following this link
https://gist.github.com/danlangford/3418696
But this is not an ideal way to do this, since by default spring should be able to read properties from application-test.properties.
And I never figured out what caused the second issue and how to solve it.
I think this would be a common issue when writing unit test on class which read properties from .properties file using $Value annotation. Any comments or suggestions would be very much appreciated.
The key point hear as said M. Deinum is that you use a mix of Spring bean and Mock Object that in this case aren't Spring bean and for this reason can't benefit of the feature of Spring Container such as the injection of the properties.
In particular you should use the spring test abstraction as a "integration test" istruments. With this words I intended that you should use this abstraction, for test the correct configuration, behavior and so on fo your bean in the spring contex. However if you use Stub or mock object you actually exit, of a smal part probably, by the management of spring and the your test don't make sense. Using stub or mock the your test become a Unit test in sense that it will be a test the your bean and functionality in isolation infact you have mock or stub the dependency of your object.
I hope that this reflection could be help you
I am glad to know there is no way to read values from properties by #Value inside a mock obj.
But still my problem is that I want to unit test my dummy method in ServiceClass. Put it another way, as long as I could unit test this method, I don't care whether #Value works or not.
Here is my solution of test method
#Profile("test")
public class ServiceClassTest extends AbstractTest {
#Value("${data.layer.url}")
private String dataLayerUrl;
#InjectMocks
private ServiceClass ServiceClass;
#Mock
RestTemplate restTemplate;
#Before
public void initializeMockito() {
MockitoAnnotations.initMocks(this);
}
#Test
public void dummyTest(){
when(restTemplate.postForObject(anyString() , eq(), eq() )).thenReturn(...);
serviceClass.dummy();
assertEquals(...);
verify(restTemplate).postForObject(anyString(), eq(), eq());
}
By using anyString, I don't rely on what value is read from properties, since I only want to test whether dummy method call restTemplate's postForObject method properly.
You need to add PropertySourcesPlaceholderConfigurer to your test configuration in order to populate properties annotated with #Value annotation. Spring Boot adds it to configuration, but since your test is running without Spring Boot you have to declare it. For more details seehere .
in place of #InjectMocks you can write #Autowired and I thing you can use both annotation like that
Case 1
#InjectMocks
private ServiceClass ServiceClass;
case 2
#Autowired
#InjectMocks
private ServiceClass ServiceClass;
I have same issue but after discussion my senior I have find above like solutions

Overriding beans in Integration tests

For my Spring-Boot app I provide a RestTemplate though a #Configuration file so I can add sensible defaults(ex Timeouts). For my integration tests I would like to mock the RestTemplate as I dont want to connect to external services - I know what responses to expect. I tried providing a different implementation in the integration-test package in the hope that the latter will override the real implementation , but checking the logs it`s the other way around : the real implementation overrides the test one. How can I make sure the one from the TestConfig is the one used?
This is my config file :
#Configuration
public class RestTemplateProvider {
private static final int DEFAULT_SERVICE_TIMEOUT = 5_000;
#Bean
public RestTemplate restTemplate(){
return new RestTemplate(buildClientConfigurationFactory());
}
private ClientHttpRequestFactory buildClientConfigurationFactory() {
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
factory.setReadTimeout(DEFAULT_SERVICE_TIMEOUT);
factory.setConnectTimeout(DEFAULT_SERVICE_TIMEOUT);
return factory;
}
}
Integration test:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = TestConfiguration.class)
#WebAppConfiguration
#ActiveProfiles("it")
public abstract class IntegrationTest {}
TestConfiguration class:
#Configuration
#Import({Application.class, MockRestTemplateConfiguration.class})
public class TestConfiguration {}
And finally MockRestTemplateConfiguration
#Configuration
public class MockRestTemplateConfiguration {
#Bean
public RestTemplate restTemplate() {
return Mockito.mock(RestTemplate.class)
}
}
Since Spring Boot 1.4.x there is an option to use #MockBean annotation to fake Spring beans.
Reaction on comment:
To keep context in cache do not use #DirtiesContext, but use #ContextConfiguration(name = "contextWithFakeBean") and it will create separate context, while it will keep default context in cache. Spring will keep both (or how many contexts you have) in cache.
Our build is this way, where most of the tests are using default non-poluted config, but we have 4-5 tests that are faking beans. Default context is nicely reused
1.
You can use #Primary annotation:
#Configuration
public class MockRestTemplateConfiguration {
#Bean
#Primary
public RestTemplate restTemplate() {
return Mockito.mock(RestTemplate.class)
}
}
BTW, I wrote blog post about faking Spring bean
2.
But I would suggest to take a look at Spring RestTemplate testing support. This would be simple example:
private MockRestServiceServer mockServer;
#Autowired
private RestTemplate restTemplate;
#Autowired
private UsersClient usersClient;
#BeforeMethod
public void init() {
mockServer = MockRestServiceServer.createServer(restTemplate);
}
#Test
public void testSingleGet() throws Exception {
// GIVEN
int testingIdentifier = 0;
mockServer.expect(requestTo(USERS_URL + "/" + testingIdentifier))
.andExpect(method(HttpMethod.GET))
.andRespond(withSuccess(TEST_RECORD0, MediaType.APPLICATION_JSON));
// WHEN
User user = usersClient.getUser(testingIdentifier);
// THEN
mockServer.verify();
assertEquals(user.getName(), USER0_NAME);
assertEquals(user.getEmail(), USER0_EMAIL);
}
More examples can be found in my Github repo here
The Problem in your configuration is that you are using #Configuration for your test configuration. This will replace your main configuration. Instead use #TestConfiguration which will append (override) your main configuration.
46.3.2 Detecting Test Configuration
If you want to customize the primary configuration, you can use a
nested #TestConfiguration class. Unlike a nested #Configuration class,
which would be used instead of your application’s primary
configuration, a nested #TestConfiguration class is used in addition
to your application’s primary configuration.
Example using SpringBoot:
Main class
#SpringBootApplication() // Will scan for #Components and #Configs in package tree
public class Main{
}
Main config
#Configuration
public void AppConfig() {
// Define any beans
}
Test config
#TestConfiguration
public void AppTestConfig(){
// override beans for testing
}
Test class
#RunWith(SpringRunner.class)
#Import(AppTestConfig.class)
#SpringBootTest
public void AppTest() {
// use #MockBean if you like
}
Note: Be aware, that all Beans will be created, even those that you override. Use #Profile if you wish not to instantiate a #Configuration.
#MockBean and bean overriding used by the OP are two complementary approaches.
You want to use #MockBean to create a mock and forget the real implementation : generally you do that for slice testing or integration testing that doesn't load some beans which class(es) you are testing depend on and that you don't want to test these beans in integration.
Spring makes them by default null, you will mock the minimal behavior for them to fulfill your test.
#WebMvcTest requires very often that strategy as you don't want to test the whole layers and #SpringBootTest may also require that if you specify only a subset of your beans configuration in the test configuration.
On the other hand, sometimes you want to perform an integration test with as many real components as possible, so you don't want to use #MockBean but you want to override slightly a behavior, a dependency or define a new scope for a bean, in this case, the approach to follow is bean overriding :
#SpringBootTest({"spring.main.allow-bean-definition-overriding=true"})
#Import(FooTest.OverrideBean.class)
public class FooTest{
#Test
public void getFoo() throws Exception {
// ...
}
#TestConfiguration
public static class OverrideBean {
// change the bean scope to SINGLETON
#Bean
#Scope(ConfigurableBeanFactory.SINGLETON)
public Bar bar() {
return new Bar();
}
// use a stub for a bean
#Bean
public FooBar BarFoo() {
return new BarFooStub();
}
// use a stub for the dependency of a bean
#Bean
public FooBar fooBar() {
return new FooBar(new StubDependency());
}
}
}
With #Primary annotation, Bean overriding works with Spring Boot 1.5.X but fails with Spring Boot 2.1.X it throw error:
Invalid bean definition with name 'testBean' defined in sample..ConfigTest$SpringConfig:..
There is already .. defined in class path resource [TestConfig.class]] bound
Please add below properties= which will instruct Spring explicitly to allow overriding, it is self explainatory.
#SpringBootTest(properties = ["spring.main.allow-bean-definition-overriding=true"])
UPDATE: You can add the same property in application-test.yml (file name depend upon what test profile name you are tests with)
Getting a little deeper into it, see my second answer.
I solved the Problem using
#SpringBootTest(classes = {AppConfiguration.class, AppTestConfiguration.class})
instead of
#Import({ AppConfiguration.class, AppTestConfiguration.class });
In my case the Test is not in the same package as the App. So I need to specify the AppConfiguration.class (or the App.class) explicit. If you use the same package in the test, than I guess you could just write
#SpringBootTest(classes = AppTestConfiguration.class)
instead of (not working)
#Import(AppTestConfiguration.class );
It is pretty wired to see that this is so different. Maybe some one can explain this. I could not find any good answers until now. You might think, #Import(...) is not picked up if #SpringBootTestsis present, but in the log the overriding bean shows up. But just the wrong way around.
By the way, using #TestConfiguration instead #Configuration also makes no difference.
I´ve declared an inner configuration class within my test because I wanted to overwrite just a single method
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class FileNotificationWebhookTest{
public static class FileNotificationWebhookTestConfiguration {
#Bean
#Primary
public FileJobRequestConverter fileJobRequestConverter() {
return new FileJobRequestConverter() {
#Override
protected File resolveWindowsPath(String path) {
return new File(path);
}
};
}
}
}
However,
Declaring the configuration in #SpringBootTest did not work:
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,classes = {FileNotificationWebhookTest.FileNotificationWebhookTestConfiguration.class})
or annotating the test configuration with #Configuration did not work:
#Configuration
public static class FileNotificationWebhookTestConfiguration {
}
and was leading to
Caused by: org.springframework.context.ApplicationContextException:
Unable to start web server; nested exception is
org.springframework.context.ApplicationContextException: Unable to
start ServletWebServerApplicationContext due to missing
ServletWebServerFactory bean.
What did work for me ( contrary to some other posts here) was using #Import
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#Import(FileNotificationWebhookTest.FileNotificationWebhookTestConfiguration.class)
class FileNotificationWebhookTest {
}
Using Spring: 5.3.3 with Spring-Boot-Starter: 2.4.2
#MockBean creates Mockito mock instead of production build.
If you do not want to use Mockito, but provide a replacement in some other way (i.e. by disabling some features of bean with feature toggles), I suggest using combination of #TestConfiguration (since Spring Boot 1.4.0) and #Primary annotation.
#TestConfiguration will load your default context and apply your #TestConfiguration piece in addition to it. Adding #Primary will force your mocked RestTemplate to be injected to it's dependents.
See simplified example below:
#SpringBootTest
public class ServiceTest {
#TestConfiguration
static class AdditionalCfg {
#Primary
#Bean
RestTemplate rt() {
return new RestTemplate() {
#Override
public String exec() {
return "Test rest template";
}
};
}
}
#Autowired
MyService myService;
#Test
void contextLoads() {
assertThat(myService.invoke()).isEqualTo("Test rest template");
}
}
This is super weird.
In my case, (Spring Boot 2.6.7), I could simply #Import MyTestConfiguration containing a custom #Primary #Bean into my #SpringBootTest, and everything worked.
Right until I needed to explicitly name my bean.
Then I suddenly had to resort to
#SpringBootTest(
properties = ["spring.main.allow-bean-definition-overriding=true"],
classes = [MyTestConfig::class],
)
Check this answer along with others provided in that thread.
It's about overriding bean in Spring Boot 2.X, where this option was disabled by default. It also has some ideas about how to use Bean Definition DSL if you decided to take that path.
The simplest solution I found was to set this property in application.properties:
spring.main.allow-bean-definition-overriding=true
This will enable overriding of beans.
Next, create a configuration class in test, and annotate your bean with:
#Bean
#Primary
This way, this bean will override your usual bean when running tests.

Categories