I'm developing a Spring Boot application and I'm having some trouble setting up RESTController tests. The problem is that, when I run the test classes individually, they all work. However, when I try to run all the test classes at once, only the first one works and the others throw java.lang.IllegalStateException: Failed to load ApplicationContext. I have been debugging this and the problematic line is the following:
INSERT INTO users(username,password, email) VALUES ('admin','$2a$10$bicbzJTFskk8.sHWJauxCu2RzDIqXk/zCxQDZ5ByLQw0m0lQ6l2Pa', 'admin#mail.com') [23505-200] Caused by: org.springframework.jdbc.datasource.init.ScriptStatementFailedException: Failed to execute SQL script statement #1 of URL [file:spring-boot/target/classes/db/hsqldb/data.sql]: INSERT INTO users(username,password, email) VALUES ('admin','$2a$10$bicbzJTFskk8.sHWJauxCu2RzDIqXk/zCxQDZ5ByLQw0m0lQ6l2Pa', 'admin#mail.com'); nested exception is org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException: Unique index or primary key violation: "PUBLIC.PRIMARY_KEY_4D ON PUBLIC.USERS(USERNAME) VALUES 1"; SQL statement:
This leads me to think that the data.sql script is being executed on every test (which I believe should not be the case since my controller tests shouldn't rely on DB data). On top of that, the data is not being flushed after executing each class, so the first one works fine and the rest throw a #Unique exception because the data is already there.
My REST controller tests look like this:
#DirtiesContext(classMode = ClassMode.BEFORE_CLASS)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#AutoConfigureMockMvc(addFilters = false)
public class GameControllerTest {
#Autowired
MockMvc mockMvc;
#MockBean
GameService gameService;
#BeforeEach
void setup() {
Game game1 = new Game();
game1.setId(1);
Game game2 = new Game();
game2.setId(2);
Lobby lobby = new Lobby();
when(gameService.findAll()).thenReturn(List.of(game1, game2));
when(gameService.createFromLobby(lobby)).thenReturn(game1);
when(gameService.gameCount()).thenReturn(List.of(game1, game2).size());
when(gameService.findGameById(1)).thenReturn(game1);
}
#Test
void testGetAllGames() throws Exception {
mockMvc.perform(get("/games")).andExpect(status().isOk()).andExpect(jsonPath("$", hasSize(2)))
.andExpect(jsonPath("$[0].id", is(1)))
.andExpect(jsonPath("$[1].id", is(2)));
}
As you can see, I also tried to use #DirtiesContext(classMode = ClassMode.BEFORE_CLASS) but it does not fix the problem.
I feel like there are two different questions in this one.
First your problem is, that your entire SpringBootApplication is started because you are using #SpringBootTest use #WebMvcTest instead. You also "need" to use
#LocalServerPort
private int port;
to actually access the port you add with (webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
Your second problem
On top of that, the data is not being flushed after executing each class, so the first one works fine and the rest throw a #Unique exception because the data is already there.
Can easily be fixed by annotating your test with #Transactional tho I don't believe this to be the solution for your actual problem.
Thanks both for your answers. I had to do the following changes:
#ExtendWith(SpringExtension.class)
#WebMvcTest(controllers = GameController.class)
public class GameControllerTest {
#Autowired
MockMvc mockMvc;
#MockBean
private GameService gameService;
// I also have to mock all the services called from gameService:
#MockBean
private UserService userService;
#MockBean
private PlayerService playerService;
#MockBean
private DataSource dataSource;
// (...)
I didn't know that I also had to mock the services that were not called directly by the service. This way, #WebMvcTest works as expected.
Related
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.
#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 am unable to understand few things while testing with JUnit, I have written a few tests, while some seem to work simply with
#RunWith(MockitoJUnitRunner.class)
and
#Mock
some seem not to work with them, and so I have to use
#RunWith(SpringRunner.class)
and
#MockBean
I understand that #MockBean is used when dealing with Spring Container, while #Mock is just to use to replicate/mock a certain class and its methods. But what would be the perfect time to use #MockBean?
#Test
public void addBulkFcmLog() throws Exception {
JacksonTester.initFields(this, new ObjectMapper());
AdminFcmResource adminFcmResource = AdminFcmResource.builder()
.adminId(123L)
.build();
given(this.fcmService.addBulkFcmLog(any(BulkFcmDataResource.class))).willReturn(adminFcmResource);
MockHttpServletResponse response = mockMvc.perform(
post(Routes.SEND_FCM + "/admin/logs/add")
.contentType(MediaType.APPLICATION_JSON)
.content(bulkFcmDataResourceJacksonTester.write(BulkFcmDataResource.builder().adminId(123L).build()).getJson()))
.andExpect(status().isCreated())
.andReturn()
.getResponse();
assertThat(response.getContentAsString()).isEqualTo(
adminFcmResourceJacksonTester.write(adminFcmResource).getJson()
);
assertThat(response.getStatus()).isEqualTo(HttpStatus.CREATED.value());
}
Above is a test I wrote for a controller in my spring boot application, but When i mock the fcmService class using #Mock, the response comes with an empty body, but then I changed It to #MockBean and Autowired the controller class then test returned the right result.
How did this happen?
In an integration-test context it is always advisable to work with beans (mocked if necessary). Few points to keep in mind:
#MockBean takes care of injecting the object, you do not need to set it yourself.
If your configuration is wrong (you did not specify a #Qualifier for example), the test will expose that problem on startup.
In an integration test you want to have your system in a state which resembles the prod scenario as close as possible, and #MockBean gets you closer than a plain #Mock
Many times the bean dependencies have no setters and manual injection would be just plain hard.
This question already has answers here:
Why is my Spring #Autowired field null?
(21 answers)
Closed 4 years ago.
Problem Summary:
A configuration object is created successfully during regular run (using #Autowired), but is null when running in unit test phase.
Edit, in response to flagging the question as duplicate:
I don't think this is a duplication of Why is my Spring #Autowired field null? as my question is about unit test phase and the above question is about regular run phase.
Detailed:
I have a configuration object that reads a properties file:
#Configuration
#ConfigurationProperties(prefix = "defaults")
#Getter
#Setter
public class DefaultsConfigProperties {
Double value;
....
In the service layer, I'm using this class successfully when running the app in regular mode:
#Service
#Configuration
public class CatsService {
#Autowired
DefaultsConfigProperties defaultsConfigProperties;
...
However, the problem is during Unit test, as the defaultsConfigProperties is null.
Here is the test class:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest
#TestPropertySource(properties = {
"defaults.value=0.2"
})
public class CatServiceTest {
private CatService;
#Before
public void before(){
catService = new CatService();
}
If I understand correctly, I need to somehow create / inject / mock the DefaultsConfigProperties class during the unit test phase.
I also know that using the #Autowired is not recommended and it is better to pass items to the constructor of the class.
So, should the solution be that the CatService will need to accept the DefaultsConfigProperties in the constructor, and then to create it (how?) in the CatServiceTest ?
You are on the right track, as you stated constructor injection is favored above field injection.
Nevertheless if you want to rely on field injection you could use Mockito.
Example for usage in a unit test:
#Mock
DefaultsConfigProperties defaultsConfigProperties;
#InjectMocks
private CatService;
#Before
public void before(){
catService = new CatService();
MockitoAnnotations.init(this);
}
So in your case you should also remove the annotations of your test class to make it a unit test.
The #SpringBootTest annotation takes a classes array parameter. You can pass any class annotated with #Configuration into this array, and also the class containing your main method (SpringApplication.run(...)) if you want to load the same ApplicationContext. In your case:
#SpringBootTest(classes = {Application.class, DefaultsConfigProperties.class})
Note you can also use SpringRunner directly.
I'm implementing a service using Spring Boot and Spring Cloud Config service to provide the configuration values. In my Service I have a couple of config values which need to refresh when the value changes in the remote Git repo, and I was using #RefreshScope to enable that feature.
The problem comes when I try to inject a mock for RestTemplate in that service, it appears to ignore it and use the autowired instance instead. If I comment out the annotation it seems to work fine.
Here's the code for the Service:
#Service
#RefreshScope
public class MyServiceImpl implements MyService {
private static final Logger LOG = Logger.getLogger(MyServiceImpl.class);
#Autowired
public RestTemplate restTemplate;
#Value("${opts.default}")
private String default;
#Value("${opts.address}")
private String address;
#Value("${opts.separator}")
private String separator;
...
}
Test source code:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Application.class)
public class ServiceTest {
#Mock
private RestTemplate restTemplate;
#Autowired
#InjectMocks
private MyServiceImpl service;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
public void testMethod() throws Exception {
when(restTemplate.postForObject(anyString(), any(), eq(ServiceResponse.class), anyMap())).thenReturn(getSuccessfulResponse());
ServiceResponse response = service.doYourStuff();
Assert.assertNotNull(response);
Assert.assertTrue(response.isSuccessful());
}
...
}
When adding the #RefreshScope the bean becomes a proxy instead of an actual raw implementation. Currently the RestTemplate is set on the proxy rather then the underlying instance. (If you debug you would see that your MyServiceImpl is actually more like an instance of MyServiceImpl$SpringCgLib#353234).
To fix you need to manually set the dependency using ReflectionTestUtils and AopTestUtils. The latter is to obtain the actual proxy.
Remove the #InjectMocks annotation and add the following to your setup method after the initialization of the mocks:
Object actualTarget = AopTestUtils.getUltimateTargetObject(service);
ReflectionTestUtils.setfield(actualTarget, "restTemplate", restTemplate);
For versions earlier as 4.2 the following might do the trick
Object actualTarget = (service instanceof Advised) ? ((Advised) service).getTargetSource().getTarget() : service;
The problem is that Mockito doesn't detect the proxy and just sets the field. The ReflectionTestUtils doesn't detect the proxy either hence the manual unwrapping. I actually stepped into this trap a couple of times before, which led me to create SPR-14050 this morning to have it embedded in the ReflectionTestUtils to easy the pain a little.