Spring Boot tests. Initilize database programatically - java

I develope a WebApp using Spring Boot. I need to make integration tests with database. I have a problem in database initilization. I know it is possible to prepare database with initilazion scripts. And I do it partially. But some records have type of BLOB and it is annoying to initilize it by the script. So I'm trying to init this records programatically from #Test method using CrudRepository implementations from ApplicationContext (that are encapsulated in PersistenceService).
#TestExecutionListeners({DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class,
TransactionalTestExecutionListener.class, DbUnitTestExecutionListener.class})
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#DatabaseSetup(MyTest.DATASET)
#DatabaseTearDown(type = DatabaseOperation.DELETE_ALL, value = { MyTest.DATASET })
#DirtiesContext
public class MyTest {
protected static final String DATASET = "classpath:dbunit/customer.xml";
#Autowired
private TestRestTemplate restTemplate;
#Autowired
private PersistenceService persistenceService;
#Test
#Transactional
#Rollback(false)
public void afterGroupDeleteCrlShouldContainsAllCertificates() throws Exception {
prepareDatabase(persistenceService);
restTemplate.delete("/customer/");
}
But marking of #Test method by #Transactional is the reason of deadlock when I'm calling restTemplate.delete() because there is uncommited transaction.
So I'm trying to commit transaction manually adding after calling of prepareDatabase(persistenceService) this snippet:
if (TestTransaction.isActive()) {
TestTransaction.flagForCommit();
TestTransaction.end();
}
This snippet fixes deadlock but generate SQLException after test execution
java.sql.SQLException: PooledConnection has already been closed.
I'm sure it is common task. But I don't know how to resolve it gracefully.

I think I'm missing some parts, but how about putting the prepareDatabase into a #Before method in your test.
#Before
public void setupDatabaseForEachTest(){
prepareDatabase(persistenceService);
}
I think that will allow your #Test to be #Transactional and give you the desired results.

Related

Initialize Mocks in test before #BeforeStep

I have a custom reader with an #BeforeStep function in order to initialize some data. These data are comming from an external database.
#Component
public class CustomReader implements ItemReader<SomeDTO> {
private RestApiService restApiService;
private SomeDTO someDTO;
#BeforeStep
private void initialize() {
someDTO = restApiService.getData();
}
#Override
public SomeDTO read() {
...
return someDTO
}
}
In my unit test i need to mock the calls to the external database.
#RunWith(SpringRunner.class)
#SpringBootTest(classes = NedBatchApplication.class)
public class CustomReaderTest {
#Autowired
CustomReader customReader;
#Mock
RestApiService restApiService;
#Before
private void setup() {
MockitoAnnotations.initMocks(this);
ReflectionTestUtils.setField(customReader, "restApiService", restApiService);
Mockito.when(restApiService.getData().thenReturn(expectedData);
}
}
The problem i am facing is the #BeforeStep is executed before the #Before from the unit test, when i lauch my Test. So restApiService.getData() returns null instead of expectedData.
Is there a way to achieve what i want or do i need to do it with a different approach ?
After some reflexion with a co-worker he gave me a solution :
#RunWith(SpringRunner.class)
#SpringBootTest(classes = NedBatchApplication.class)
public class CustomReaderTest {
CustomReader customReader;
#Mock
RestApiService restApiService;
#Before
private void setup() {
MockitoAnnotations.initMocks(this);
Mockito.when(restApiService.getData().thenReturn(expectedData);
this.customReader = new CustomReader(restApiService);
}
#Test
public void test() {
customReader.initialize();
(...)
}
}
Are you certain that the BeforeStep is running before the Before annotation (by using logging or similar?).
It's possible your Mockito invocation is not fully correct. Try using Mockito.doReturn(expectedData).when(restApiService).getData() instead.
As an alternative approach, if the RestApiService was autowired in your custom reader, you'd be able to use the #InjectMocks annotation on the custom reader declaration in your test, which would cause the mocked version of your restApiService to be injected to the class during the test.
Usually when using Spring based tests, try to make dependencies like restApiService (the ones you would like to mock) to be spring beans, and then you can instruct spring to create mock and inject into application context during the application context creation with the help of #MockBean annotation:
import org.springframework.boot.test.mock.mockito.MockBean;
#RunWith(SpringRunner.class)
#SpringBootTest(classes = NedBatchApplication.class)
public class CustomReaderTest {
#MockBean
private RestApiService restApiService;
}

Is it good practice to use SpringRunner without SpringContext when writing JUnit test cases?

I tried junit with mockito, and wrote some test cases for a coding exercise.
Here is the test case which i wrote:
#RunWith(SpringRunner.class)
public class TransactionControllerTest {
#Mock
TransactionService transactionServiceMock;
#InjectMocks
TransactionController transactionController;
TransactionRequest txn = new TransactionRequest("123.34", "2018-11-28T23:32:36.312Z");
#Test
public void testSaveTxn() throws Exception {
Mockito.when(transactionServiceMock.saveTxn(Mockito.any(TransactionRequest.class))).thenReturn(true);
ResponseEntity<?> responseEntity = transactionController.saveTxn(null, txn);
assertTrue(responseEntity.getStatusCode().equals(HttpStatus.CREATED));
}
#Test
public void testGetStats() throws Exception {
StatsResponse sr = new StatsResponse("0.00", "0.00", "0.00", "0.00", 0L);
Mockito.when(transactionServiceMock.getStats()).thenReturn(sr);
ResponseEntity<StatsResponse> responseEntity = (ResponseEntity<StatsResponse>) transactionController.getStats(null);
System.out.println("sr response = "+responseEntity.getBody());
assertTrue(responseEntity.getBody().equals(sr));
}
#Test
public void testDelete() throws Exception {
Mockito.doNothing().when(transactionServiceMock).delete();
ResponseEntity<HttpStatus> responseEntity = (ResponseEntity<HttpStatus>) transactionController.deleteTxn(null);
System.out.println("sr response = "+responseEntity.getBody());
assertTrue(responseEntity.getStatusCode().equals(HttpStatus.NO_CONTENT));
}
}
The test cases were working fine.
But my application was rejected specifying the following reason:
You were using SpringRunner even though you are not using SpringContext in the tests, and mocking everything.
Now, following are my concerns:
What's wrong with the test cases?
What is the meaning of above rejection reason?
How can i correct that?
What's wrong with the test cases?
I think what they want you to do is to write a spring web layer test. This is not a spring MVC test/spring-boot test. Because you don't test the controller as a spring loaded resource. You test it as a simple java class. That won't prove whether it behaves as a Spring controller correctly. You won't be able to test features such as;
spring resource injection
request dispatching and validation
How can i correct that?
You have to write a spring MVC test and use MockMvc or RestTemplate to verify your controller. For example;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = YourContext.class)
#WebAppConfiguration
public class MyWebTests {
#Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
#Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
#Test
public void foo() throws Exception {
mockMvc.perform(get("/status"));
//and verification
}
}
Usage of mockito mocks is not the worst idea, but you could have used auto wired #MockBeans.
If this is spring-boot, you will have more flexibility. Have a look at following resources.
https://docs.spring.io/spring/docs/current/spring-framework-reference/testing.html
https://spring.io/guides/gs/testing-web/
You have complaint because you don't need spring's test features in your test.
Your test is pure unit test.
So if you will remove #RunWith(SpringRunner.class) nothing will be changed for your test. Just put there #ExtendWith(MockitoExtension.class)
SpringRunner will initialize spring context for you test that you could inject or mock slice of your application using following annotations:
#MockBean
#Autowired
etc..

Using #PostConstruct in a test class causes it to be called more than once

I am writing integration tests to test my endpoints and need to setup a User in the database right after construct so the Spring Security Test annotation #WithUserDetails has a user to collect from the database.
My class setup is like this:
#RunWith(value = SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#AutoConfigureMockMvc
#WithUserDetails(value = "email#address.com")
public abstract class IntegrationTests {
#Autowired
private MockMvc mockMvc;
#Autowired
private Service aService;
#PostConstruct
private void postConstruct() throws UserCreationException {
// Setup and save user data to the db using autowired service "aService"
RestAssuredMockMvc.mockMvc(mockMvc);
}
#Test
public void testA() {
// Some test
}
#Test
public void testB() {
// Some test
}
#Test
public void testC() {
// Some test
}
}
However the #PostConstruct method is called for every annotated #Test, even though we are not instantiating the main class again.
Because we use Spring Security Test (#WithUserDetails) we need the user persisted to the database BEFORE we can use the JUnit annotation #Before. We cannot use #BeforeClass either because we rely on the #Autowired service: aService.
A solution I found would be to use a variable to determine if we have already setup the data (see below) but this feels dirty and that there would be a better way.
#PostConstruct
private void postConstruct() throws UserCreationException {
if (!setupData) {
// Setup and save user data to the db using autowired service "aService"
RestAssuredMockMvc.mockMvc(mockMvc);
setupData = true;
}
}
TLDR : Keep your way for the moment. If later the boolean flag is repeated in multiple test classes create your own TestExecutionListener.
In JUnit, the test class constructor is invoked at each test method executed.
So it makes sense that #PostConstruct be invoked for each test method.
According to JUnit and Spring functioning, your workaround is not bad. Specifically because you do it in the base test class.
As less dirty way, you could annotate your test class with #TestExecutionListeners and provide a custom TestExecutionListener but it seem overkill here as you use it once.
In a context where you don't have/want the base class and you want to add your boolean flag in multiple classes, using a custom TestExecutionListener can make sense.
Here is an example.
Custom TestExecutionListener :
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.support.AbstractTestExecutionListener;
public class MyMockUserTestExecutionListener extends AbstractTestExecutionListener{
#Override
public void beforeTestClass(TestContext testContext) throws Exception {
MyService myService = testContext.getApplicationContext().getBean(MyService.class);
// ... do my init
}
}
Test class updated :
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#AutoConfigureMockMvc
#WithUserDetails(value = "email#address.com")
#TestExecutionListeners(mergeMode = MergeMode.MERGE_WITH_DEFAULTS,
value=MyMockUserTestExecutionListener.class)
public abstract class IntegrationTests {
...
}
Note that MergeMode.MERGE_WITH_DEFAULTS matters if you want to merge TestExecutionListeners coming from the Spring Boot test class with TestExecutionListeners defined in the #TestExecutionListeners of the current class.
The default value is MergeMode.REPLACE_DEFAULTS.

JUnit rollback transaction with #Async method

I am writing an integration test using SpringJUnit4ClassRunner.
I have a base class:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration({ /*my XML files here*/})
#Ignore
public class BaseIntegrationWebappTestRunner {
#Autowired
protected WebApplicationContext wac;
#Autowired
protected MockServletContext servletContext;
#Autowired
protected MockHttpSession session;
#Autowired
protected MockHttpServletRequest request;
#Autowired
protected MockHttpServletResponse response;
#Autowired
protected ServletWebRequest webRequest;
#Autowired
private ResponseTypeFilter responseTypeFilter;
protected MockMvc mockMvc;
#BeforeClass
public static void setUpBeforeClass() {
}
#AfterClass
public static void tearDownAfterClass() {
}
#Before
public void setUp() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).addFilter(responseTypeFilter).build();
}
#After
public void tearDown() {
this.mockMvc = null;
}
}
Then I extend it and create a test using mockMvc:
public class MyTestIT extends BaseMCTIntegrationWebappTestRunner {
#Test
#Transactional("jpaTransactionManager")
public void test() throws Exception {
MvcResult result = mockMvc
.perform(
post("/myUrl")
.contentType(MediaType.APPLICATION_XML)
.characterEncoding("UTF-8")
.content("content")
.headers(getHeaders())
).andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_XML))
.andExpect(content().encoding("ISO-8859-1"))
.andExpect(xpath("/*[local-name() ='myXPath']/")
.string("result"))
.andReturn();
}
In the end of the flow, an entity is saved into DB. But the requirement here is that is should be done asynchronously. So consider this method is called:
#Component
public class AsyncWriter {
#Autowired
private HistoryWriter historyWriter;
#Async
public void saveHistoryAsync(final Context context) {
History history = historyWriter.saveHistory(context);
}
}
Then HistoryWriter is called:
#Component
public class HistoryWriter {
#Autowired
private HistoryRepository historyRepository;
#Transactional("jpaTransactionManager")
public History saveHistory(final Context context) {
History history = null;
if (context != null) {
try {
history = historyRepository.saveAndFlush(getHistoryFromContext(context));
} catch (Throwable e) {
LOGGER.error(String.format("Cannot save history for context: [%s] ", context), e);
}
}
return history;
}
}
The problem with all this is that after test is done, History object is left in the DB. I need to make test transaction to rollback all changes in the end.
Now, what I've tried so far:
Remove #Async annotation. Obviously, this cannot be the solution, but was done to confirm rollback will be perform without it. Indeed, it is.
Move #Async annotation to HistoryWriter.saveHistory() method to have it in one place with #Transactional. This article https://dzone.com/articles/spring-async-and-transaction suggests it should work this way, but for me, no rollback is done after test.
Swap places of these two annotations. It does not give the desired result as well.
Does anyone have any idea how to force rollback of the DB changes made in asynchronous method?
Side notes:
Transaction configuration:
<tx:annotation-driven proxy-target-class="true" transaction- manager="jpaTransactionManager"/>
Async configuration:
<task:executor id="executorWithPoolSizeRange" pool-size="50-75" queue-capacity="1000" />
<task:annotation-driven executor="executorWithPoolSizeRange" scheduler="taskScheduler"/>
Does anyone have any idea how to force rollback of the DB changes made in asynchronous method?
That is unfortunately not possible.
Spring manages transaction state via ThreadLocal variables. A transaction started in another thread (e.g., the one created for your #Async method invocation) can therefore not participate in a transaction managed for the parent thread.
This means that the transaction used by your #Async method is never the same as the test-managed transaction which gets automatically rolled back by the Spring TestContext Framework.
Thus, the only possible solution to your problem is to manually undo the changes to the database. You can do this using JdbcTestUtils to execute an SQL script programmatically within an #AfterTransaction method, or you can alternatively configure an SQL script to be executed declaratively via Spring's #Sql annotation (using the after execution phase). For the latter, see How to execute #Sql before a #Before method for details.
Regards,
Sam (author of the Spring TestContext Framework)

How to autowire field in static #BeforeClass?

#RunWith(SpringJUnit4ClassRunner.class)
public void ITest {
#Autowired
private EntityRepository dao;
#BeforeClass
public static void init() {
dao.save(initialEntity); //not possible as field is not static
}
}
How can I have my service injected already in the static init class?
With Junit 5 you can do this (#BeforeAll instead of #BeforeClass)
public void ITest {
#Autowired
private EntityRepository dao;
#BeforeAll
public static void init(#Autowired EntityRepository dao) {
dao.save(initialEntity); //possible now as autowired function parameter is used
}
}
By leaving the field it means it can be used in other tests
One workaround that I have been using to get this working is to use #Before with a flag to skip it being executed for each testcase
#RunWith(SpringJUnit4ClassRunner.class)
public class BaseTest {
#Autowired
private Service1 service1;
#Autowired
private Service2 service2;
private static boolean dataLoaded = false;
#Before
public void setUp() throws Exception {
if (!dataLoaded) {
service1.something();
service2.somethingElse();
dataLoaded = true;
}
}
}
UPD for Spring 2.x versions.
Spring 2.x supports new feature a SpringExtension for Junit 5 Jupiter, where all you have to do is:
Declare your test class with #ExtendWith(SpringExtension.class)
Inject your #BeforeAll (replacement for #BeforeClass in JUnit 5) with the bean
For example:
#ExtendWith(SpringExtension.class)
...
public void ITest {
#BeforeAll
public static void init(#Autowired EntityRepository dao) {
dao.save(initialEntity);
}
}
Assuming you correctly configured JUnit 5 Jupiter with Spring 2.x
More about it here: https://docs.spring.io/spring/docs/current/spring-framework-reference/testing.html#testcontext-junit-jupiter-extension
It looks to me that you are trying to populate DB before tests.
I would give a try to two options:
If you can extract initial scripts to sql file (if that is option for you without using repository bean) you can use this approach and annotate your test with #Sql
You can explore DbUnit and here is link to spring dbunit connector which is doing exactly that and helping you populate DB before tests. Here is a github link for integrating between spring test framework and dbunit. After you do that you have #DatabaseSetup and #DatabaseTearDown which will do thing on DB you need
I know that this does not answer how to inject bean in static #BeforeClass but form code it looks it is solving your problem.
Update:
I recently run into same problem in my project and dug out this article which helped me and I think it is elegant way of dealing with this type of problem. You can extend SpringJUnit4ClassRunner with listener which can do instance level setup with all your defined beans.
To answer this question we should recap Spring 2.x versions.
If you want to "autowire" a bean in your #BeforeTest class you can use the ApplicationContext interface. Let's see an example:
#BeforeClass
public static void init() {
ApplicationContext context = new ClassPathXmlApplicationContext("application-context.xml");
EntityRepository dao2 = (EntityRepository) context.getBean("dao");
List<EntityRepository> all = dao2.getAll();
Assert.assertNotNull(all);
}
What's happening: using the ClassPathXmlApplicationContext we are instantiating all beans contained in the application-context.xml file.
With context.getBean() we read the bean specified (it must match the name of the bean!); and then you can use it for your initialization.
You should give to the bean another name (that's the dao2!) otherwise Spring normal "autowired" cannot work on the predefined bean.
As a side note, if your test extends AbstractTransactionalJUnit4SpringContextTests you can do some initialization using executeSqlScript(sqlResourcePath, continueOnError); method, so you don't depend on a class/method that you also have to test separately.
If you just want to use some DB data in your tests, you could also mock the repository and use the #Before workaround Narain Mittal describes:
#RunWith(SpringJUnit4ClassRunner.class)
public void ITest {
#MockBean
private EntityRepository dao;
#Before
public static void init() {
when(dao.save(any())).thenReturn(initialEntity);
}
}

Categories