Trying to #Autowired a RestTemplate in Spring Boot 2.1.4 using RestTemplateBuilder.
When i run junit tests i get an error when trying to autowired RestTemplate.
I saw here: How to autowire RestTemplate using annotations
It seems that RestTemplateBuilder is better so i would like to use that.
This is the configuration file :
#Configuration
public class Beans {
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.build();
}
}
This is the test class:
#RunWith(SpringRunner.class)
#SpringBootTest
#ContextConfiguration(classes = Beans.class)
public class AppTest extends TestCase {
#Autowired
private RestTemplate restTemplate;
}
The error is :
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of method restTemplate in beanDeclerations.Beans required a bean of type 'org.springframework.boot.web.client.RestTemplateBuilder' that could not be found.
The injection point has the following annotations:
- #org.springframework.beans.factory.annotation.Autowired(required=true)
Action:
Consider defining a bean of type 'org.springframework.boot.web.client.RestTemplateBuilder' in your configuration.
I edited out other autowired that work.
What am i missing here?
After searching the web i found out that spring auto wired RestTemplateBuilder, why isn't doing so here?
EDIT:
I ended up using #RestClientTest() and had to move RestTemplateBuilder Bean to the main class for now, I'll move it later to a different place.
Thanks for the help.
The RestTemplateBuilder should be available through an autocofiguration (see: https://github.com/spring-projects/spring-boot/blob/master/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/client/RestTemplateAutoConfiguration.java). I think this config is missing because of your #ContextConfiguration. You have some possibilities. Try to add the AutoConfig for the RestTemplateBuilder to your ContextConfiguration. Second one is to make a TestConfiguration and create your own RestTemplateBuilder or directly a RestTemplate. The third one is don't inject the RestTemplate - build it by hand in your test. You can also remove the #ContextConfiguration-Annotation but this will result in a test which loads every config you have defined in your project.
There's also a RestTestTemplate (https://www.baeldung.com/spring-boot-testresttemplate) which is designed for tests.
#RunWith(SpringRunner.class)
#SpringBootTest
#ContextConfiguration(classes = {TestConfig.class, RestTemplateAutoConfiguration.class})
public class SandboxApplicationTests {
#Autowired
RestTemplate restTemplate;
#Test
public void contextLoads() {
}
}
The snippet above works for me. Without the RestTemplateAutoConfiguration in the ContextConfiguration the RestTemplate can't be created because of the missing RestTemplateBuilder-Bean
I had the same issue (in an Spring Boot 2.5.5 application).
To solve it I had to add #AutoConfigureWebClient on my test class.
So my test looks like this :
#AutoConfigureWebClient
#WebMvcTest(controllers = TeamController.class)
public class TeamControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private FooService fooService;
#Test
public void findAllFoos() throws Exception {
mockMvc.perform(get("/foos"))
.andExpect(status().isOk());
}
}
But I read somewhere it may be considered as a bug that should be fixed by Spring Team (maybe will this annotation be directly added in #WebMvcTest ?).
I think this way: the test starts and stops faster than when you use #SpringBootTest (because that last one makes your application start entirely).
For more info : https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/autoconfigure/web/client/AutoConfigureWebClient.html
Related
I need to write an e2e test on REST level, that sends real requests. So I want to use application context instead of mocking beans.
RestController.class has an autowired MyService.class, and this MyService.class is dependent on two repository classes. So I tried to mock repositories and inject them in the real Service in the following way:
#ExtendWith(SpringExtension.class)
#SpringBootTest(classes = MyService.class)
#AutoConfigureMockMvc
class MyControllerTest {
#Mock private MyRepository repository;
#Mock private AnotherRepository anotherRepository;
#Autowired #InjectMocks private MyService service;
#InjectMocks private MyController controller;
#RepeatedTest(1)
void someTest() throws Exception {
MockHttpServletResponse response =
mvc.perform(...); assertThat(...);
}
}
#Service
#RequiredArgsConstructor
public class MyService {
private final MyRepository repository;
private final AnotherRepository another; ...}
But I get the following error:
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'myRepository'
I also tried to use #ContextConfiguration(classes = {MyConfig.class }) with no success:
#EnableWebMvc
#Configuration
public class MyConfig {
#Autowired private MyService service;
#Mock private MyRepository repository;
#Mock private AnotherRepository another;
}
Is there something I'm missing?
First off you should not use #Mock which is relevant for plain unit testing with mockito, use #MockBean instead. #MockBean "belongs" to the spring universe, it will create a bean (backed by mockito after all) but also will place it into the application context. If there is another bean of this type - it will substitute it.
I also don't think you should use #ExtendWith because #SpringBootTest in the relatively recent spring boot versions already has it defined (check the source code of #SpringBootTest annotation to make sure that's the case indeed.
Now other mistakes are more complicated.
To start with you don't need a spring boot test at all here. It also has a wrong parameter actually (which should point on a configuration class).
You should use #WebMvcTest instead. It seems that you only want to test the controller, and mock the service. #SpringBootTest in general tries to mimick the loading of the whole application, whereas the #WebMvcTest only tests a web layer. BTW, with this approach you don't need to even mock the Repository class because the controller will use the mock of the service, and since you don't load the whole application context there is no need to define a repository at all (the service being a mock doesn't use it anyway)
Add #RunWith(SpringRunner.class) to class MyControllerTest
#RunWith(SpringRunner.class)
class MyControllerTest {
}
I want to create JUnkt test for this endpoint:
#Autowired
private JwtTokenProvider jwtTokenProvider;
#PostMapping("reset_token")
public ResponseEntity<?> resetToken(#Valid #RequestBody ResetPasswordTokenDTO resetPasswordTokenDTO, BindingResult bindResult) {
final String login = jwtTokenProvider.getUsername(resetPasswordTokenDTO.getResetPasswordToken());
}
Full code: Github
JUnit test:
#Test
public void resetTokenTest_NOT_FOUND() throws Exception {
when(usersService.findByResetPasswordToken(anyString())).thenReturn(Optional.empty());
mockMvc.perform(post("/users/reset_token")
.contentType(MediaType.APPLICATION_JSON)
.content(ResetPasswordTokenDTO))
.andExpect(status().isNotFound());
}
I get NPE at this line when I run the code:
final String login = jwtTokenProvider.getUsername(resetPasswordTokenDTO.getResetPasswordToken());
How I can mock jwtTokenProvider properly? As you can see I have a file with test data which I load but the token is not extracted. Do you know how I can fix this issue?
The most straightforward way is to use Mockito and create mock instances and pass it directly to your controller class using constructor injection.
However, if you do not wish to use constructor injection (I recommend you to use it though, as it is much more explicit) you need to define your beans in a separate test configuration class
#Profile("test")
#Configuration
public class TestConfiguration {
#Bean
public JwtTokenProvider mockJwtTokenProvider() {
return Mockito.mock(JwtTokenProvider.class);
}
}
Also, add the correct profile to your test class by #ActiveProfiles("test")
You can consider using a #MockBean directly in your test class to mock your JwtTokenProvider. #MockBean annotation is Spring-ish and is included in spring-boot-starter-test. The Spring Boot documentation summarizes it well:
Spring Boot includes a #MockBean annotation that can be used to define
a Mockito mock for a bean inside your ApplicationContext. You can use
the annotation to add new beans or replace a single existing bean
definition. The annotation can be used directly on test classes, on
fields within your test, or on #Configuration classes and fields. When
used on a field, the instance of the created mock is also injected.
Mock beans are automatically reset after each test method.
The #MockBean annotation will make Spring look for an existing single bean of type JwtTokenProvider in its application context. If it exists, the mock will replace that bean, and if it does not exist, it adds the new mock in the application context.
Your test class would look like this:
import org.springframework.boot.test.mock.mockito.MockBean;
#MockBean
#Qualifier("xxx") //If there is more than one bean of type JwtTokenProvider
private JwtTokenProvider jwtTokenProvider;
#Test
public void resetTokenTest_NOT_FOUND() throws Exception {
when(jwtTokenProvider.getUsername(anyString())).thenReturn(Optional.empty());
mockMvc.perform(post("/users/reset_token")
.contentType(MediaType.APPLICATION_JSON)
.content(ResetPasswordTokenDTO))
.andExpect(status().isNotFound());
}
You might also want to check this and this.
Consider the following basic Spring Boot application:
#SpringBootApplication
#ComponentScan(basePackages = "webmvctestproblem.foo")
public class Application {
public static void main(String[] args) {
run(Application.class, args);
}
}
It contains only two other beans. One controller:
#RestController
class Greeter {
#GetMapping("/")
String greet() {
return "Hello, world!";
}
}
And one configuration in webmvctestproblem.foo containing a DataSource dependency:
#Configuration
class Bar {
#Autowired
private DataSource dataSource;
}
Running the application normally (through gradlew bootrun, e.g.) succeeds. Thus, confirming that the app is configured correctly under normal conditions.
However, running the following test causes a runtime error because Spring still attempts to resolve the data source bean dependency on the configuration class:
#RunWith(SpringRunner.class)
#WebMvcTest
public class GreeterTest {
#Test
public void test() throws Exception {
}
}
Of course, there isn't one to resolve because the test is a #WebMvcTest that is designed to create only MVC-related beans.
How do I get rid of the error? I have already tried excluding the configuration class using the excludeFilters attribute of the existing #WebMvcTest annotation and a new #ComponentScan annotation on the test itself. I don't want to resort to turning it into an integration test with #SpringBootTest.
(The project is also available on GitHub for convenience.)
If the DataSource is not mandatory for the test run, simply mock the DataSource with #MockBean in the test class.
#RunWith(SpringRunner.class)
#WebMvcTest
public class GreeterTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private DataSource dataSource;
#Test
public void shouldGreet() throws Exception {
mockMvc
.perform(get("/"))
.andExpect(content().string("Hello, world!"));
}
}
Spring will automatically create a Mock for DataSource and inject it into the running test application context.
Based on your source code it works.
(Btw: Your source code has a minor issue. The Greeter controller class is in the base package but the component scan only scans on the "foo" package. So there will be no Greeter controller on the test run if this isn't fixed.)
#WebMvcTest creates a "slice" of all the beans relevant to WebMvc Testing (Controllers, Json conversion related stuff and so forth).
You can examine the defaults in org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTypeExcludeFilter
In order to find which beans are actually supposed to be Run Spring must resolve them somehow, right?
So spring test tries to understand what should be loaded and what not by passing through these filters.
Now, if you mark anything with #Configuration spring "knows" that this is the place where the place should be found. So it will load the configuration and then will check which beans defined in this configuration must actually be loaded. However the object of configuration itself must be loaded anyway.
Now the process of loading the configuration object includes injecting stuff into these configurations - this is lifecycle of object creation of spring.
And this is a source of mistake here:
#Configuration
class Bar {
#Autowired
private DataSource dataSource;
}
Spring loads Bar and tries as a part of loading this object to autowire the data source. This fails since the DataSource Bean itself is excluded by filters.
Now in terms of solution:
First of all, why do you need this DataSource to be autowired in the Configuration object? Probably you have the bean that uses it, lets call it "MyDao", otherwise I don't see a point of such a construction, since #Configuration-s are basically a place to define bean and you shouldn't put business logic there (if you do - ask a separate question and me/our colleagues will try to help and suggest better implementation).
So I assume you have something like this:
public class MyDao {
private final DataSource dataSource;
public MyDao(DataSource dataSource) {
this.dataSource = dataSource;
}
}
#Configuration
class Bar {
#Autowired
private DataSource dataSource;
#Bean
public MyDao myDao() {
return new MyDao(dataSource);
}
}
In this case however you can rewrite the configuration in a different way:
#Configuration
class Bar {
// Note, that now there is no autowired datasource and I inject the parameter in the bean instead - so that the DataSource will be required only if Spring will have to create that MyDao bean (which it won't obviously)
#Bean
public MyDao myDao(DataSource dataSource) {
return new MyDao(dataSource);
}
}
Now the Bar object will still be created - as I've explained above, but it beans including MyDao of course won't be created, problem solved!
The solution with #Autowired(required=false) provided by #Anish B. should also work - spring will attempt to autowire but won't fail because the data source is unavailable, however you should think whether its an appropriate way to deal with this issue, your decision...
Before you can #Autowire the DataSource bean you need to define the DataSource in some config class or in the properties file. Something like this
spring.datasource.url = jdbc:mysql://localhost/abc
spring.datasource.name=testme
spring.datasource.username=xxxx
spring.datasource.password=xxxx
spring.datasource.driver-class-name= com.mysql.jdbc.Driver
spring.jpa.database=mysql
spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect
Or
#Configuration
public class JpaConfig {
#Bean
public DataSource getDataSource()
{
DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
dataSourceBuilder.driverClassName("org.h2.Driver");
dataSourceBuilder.url("jdbc:h2:file:C:/temp/test");
dataSourceBuilder.username("sa");
dataSourceBuilder.password("");
return dataSourceBuilder.build();
}
You should use
#AutoConfigureMockMvc
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#RunWith(SpringRunner.class)
on your test class, then you can inject
#Autowired
private MockMvc mockMvc;
and use it in test
mockMvc.perform(get("/"))
.andExpect(status().isOk())
.andReturn();
i suggest you read the documentation about testing. You can test a spring boot application in 100s of different ways.
WebMvcTest
as suggested by the documentation try, defining what controller class that you want to test in the #WebMvcTest annotation.
#RunWith(SpringRunner.class)
#WebMvcTest(Greeter.class)
public class GreeterTest {
#Autowired
private MockMvc mockMvc;
#Test
public void shouldGreet() throws Exception {
mockMvc
.perform(get("/"))
.andExpect(content().string("Hello, world!"));
}
}
#RunWith(SpringRunner.class)
#SpringBootTest(classes=MyApplication.class)
#TestPropertySource(locations = "classpath:test-application.properties")
#WebAppConfiguration
#RestClientTest(Controller.class)
public class MyIntegrationTest {
}
when I run this I get the following error
java.lang.IllegalStateException: Configuration error: found multiple declarations of #BootstrapWith for test class
The root cause is you are using both #SpringBootTest and #RestClientTest together.
Because it causes the conflict about #BootstrapWith annotation. You can see the image below.
Tip: If you are using JUnit 4, don’t forget to also add
#RunWith(SpringRunner.class) to your test, otherwise the annotations
will be ignored. If you are using JUnit 5, there’s no need to add the
equivalent #ExtendWith(SpringExtension.class) as #SpringBootTest and
the other #…Test annotations are already annotated with it.
Reference: https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.testing
If your test case is simple, you can refer to the example using #RestClientTest here.
In my case, I define some complicated configurations related to the tested service (Ex: jpaAuditingHandler, jpaMappingContext, ...). That's why I use #SpringBootTest to auto-configure.
This is my code sample:
#SpringBootTest
class MyTest {
#Autowired
private MyProperties myProperties;
#Autowired
private MyService myService;
private MockRestServiceServer server;
#Autowired
#Qualifier("restTemplateBean1")
private RestTemplate restTemplate;
#BeforeEach
void setUp() {
server = MockRestServiceServer.createServer(restTemplate);
}
#Test
void testCallRestServiceSuccess() throws Exception {
this.server.expect(requestTo(myProperties.getUrl())).andRespond(withStatus(HttpStatus.OK));
boolean result = myService.callRestService();
assertThat(result).isTrue();
}
}
Some other references:
MockRestResponseCreators javadoc: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/test/web/client/response/MockRestResponseCreators.html
https://www.baeldung.com/restclienttest-in-spring-boot
It looks like you have at least one too many Spring test annotations on your test.
What exactly do you want to test? If it is, indeed, just a RestClientTest, then this should work:
#RunWith(SpringRunner.class)
#TestPropertySource(locations = "classpath:test-application.properties")
#RestClientTest(YourRestClient.class)
public class MyIntegrationTest {
}
Or, go with just #SpringBootTest, but I don't know exactly what you want to do here.
I've generated a Spring Boot web application using Spring Initializr, using embedded Tomcat + Thymeleaf template engine, and package as an executable JAR file.
Technologies used :
Spring Boot 1.4.2.RELEASE, Spring 4.3.4.RELEASE, Thymeleaf 2.1.5.RELEASE, Tomcat Embed 8.5.6, Maven 3, Java 8
I have this class
I have this junit test class:
#ContextConfiguration(classes={TestPersistenceConfig.class})
#RunWith(SpringRunner.class)
#SpringBootTest
public class JdbcRemoteUnitRepositoryTests {
#Autowired
private JdbcRemoteUnitRepository repository;
#Autowired
#InjectMocks
private SmsConfig smsConfig;
#Autowired
#InjectMocks
private NextelSMSSender smsService;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
#Test
public void testGetAllUnits() throws DataAccessException, SQLException {
Assert.assertTrue(true);
}
}
the class NextelSMSSender:
#Service("smsService")
public class NextelSMSSender {
public final static String OK_STATUS_CODE = "200";
#Autowired
SmsConfig smsConfig;
..
}
.
#Configuration
#PropertySource("sms-gateway.properties")
public class SmsConfig {
#Value("${sms.domainId}")
private String domainId;
#Value("${sms.gateway.url}")
private String gatewayUrl;
..
}
But it seems that is not mocking the objects property, because when I package the app. I got this error:
***************************
APPLICATION FAILED TO START
***************************
Description:
Field smsConfig in com.plats.bruts.NextelSMSSender required a bean of type 'com.plats.bruts.config.SmsConfig' that could not be found.
Action:
Consider defining a bean of type 'com.plats.bruts.config.SmsConfig' in your configuration.
I created a mock bean named TestSmsConfig, tried to add #Bean, but I got a compilation error:
The annotation #Bean is disallowed for this
location
#Configuration
#Bean
public class TestSmsConfig {
#Value("fake.domainId")
private String domainId;
#Value("fake.sms.gateway.url")
private String gatewayUrl;
...
}
You're autowiring smsConfig, but you do not appear to provide that #Bean in your test application context.
In addition, you are using #InjectMocks incorrectly - this should be used to inject mock objects into the class under test (your NextelSMSSender class, not SmsConfig).
To resolve, add a #Bean method in your configuration class to provide SmsConfig bean instances.
Replace #InjectMocks from the SmsConfig variable in your test class with #MockBean. SmsConfig is an autowired mock object, and not the class under test.
See Section 41.3.4 - Of the Spring Boot Testing Features for more info:
Spring Boot includes a #MockBean annotation that can be used to define
a Mockito mock for a bean inside your ApplicationContext. You can use
the annotation to add new beans, or replace a single existing bean
definition. The annotation can be used directly on test classes, on
fields within your test, or on #Configuration classes and fields. When
used on a field, the instance of the created mock will also be
injected. Mock beans are automatically reset after each test method.
OK, so this is your configuration:
#Configuration
#PropertySource("sms-gateway.properties")
public class SmsConfig {
#Value("${sms.domainId}")
private String domainId;
#Value("${sms.gateway.url}")
private String gatewayUrl;
..
}
If you want to use that configuration in your test, you should add it to your configuration classes...
#ContextConfiguration(classes={TestPersistenceConfig.class, SmsConfig.class})
And not add it as #Autowired or whatever.