Is it possible to fail fast Spring tests if the application cannot start? - spring

I have this base test class in one Spring Boot module:
#ActiveProfiles("test")
#SpringBootTest(classes = {WebServiceApplication.class}, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
public class BaseWebServiceTest {
//...
}
If for some reason the application cannot start (in my case, if localstack docker image is not started of if Spring Cloud Contract stubs are not available, etc) the tests are still being run and obviously they all fail. Is there any way of skipping all tests if ApplicationContext is not loaded?

Is there any way of skipping all tests if ApplicationContext is not loaded?
No. There is no way to skip tests automatically if the ApplicationContext does not load.
However, you could make use of JUnit 4's assumption support and abort the execution of the tests based on some boolean condition that you choose. For example, if you can check whether the docker image started, you could then do something similar to the following.
public static boolean dockerImagedStarted() {
// return true if the Docker image started...
}
#BeforeClass
public static void ensureDockerImageStarted() {
org.junit.Assume.assumeTrue(dockerImagedStarted());
}
p.s. please note that there is an open JIRA issue requesting a built-in feature to avoid repeated attempts to load an ApplicationContext. See SPR-9548 for details.

You can create a runner that will ignore your tests in case that the ApplicationContext is not loaded
public class CustomRunner extends BlockJUnit4ClassRunner {
public CustomRunner(Class<?> clazz) throws InitializationError {
super(clazz);
}
#Override
protected boolean isIgnored(FrameworkMethod child) {
return shouldIgnore() || super.isIgnored(child);
}
/**
*
* #return if your test should be ignored or not
*/
private boolean shouldIgnore() {
// Some check if your docker is up
return true;
}
}
And the use #RunWith with the CustomRunner you have created
#RunWith(CustomRunner.class)
#ActiveProfiles("test")
#SpringBootTest(classes = {WebServiceApplication.class}, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
public class BaseWebServiceTest {
//...
}

Related

Launch application before tests on condition

I have rest-assured tests marked with #SpringBootTest (which allows to start application before executing tests). But I have two use cases:
Run tests locally (current setup is ok for that).
Run same tests on already running host (not ok, as application is started before executing tests).
Is there any way to sometimes ignore #SpringBootTest or somehow turn off application initialization?
Adding minimal example:
#RunWith(SpringRunner.class)
//In case service.host provided: no need to run application
#SpringBootTest
public class RestTest {
#Value("service.host")
private String host;
#Before
public void setUp() {
if (StringUtils.isNotEmpty(host)) {
RestAssured.baseURI = host;
}
}
#Test
public void test() {
RestAssured.get("/test").then().assertThat().statusCode(HttpStatus.OK.value());
}
}

Is there a way to get a reference to the Spring application context inside a TestExecutionListener?

I'm running end to end tests with JUnit and Selenium, the application under test is often misconfigured and I want to be able to run a setup once before all tests.
How can I get a reference to the application context of my tests from within a TestExecutionListener?
I use the #SpringJUnitConfig extension to run my tests.
import static org.junit.jupiter.api.Assertions.fail;
import org.junit.jupiter.api.Test;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
#SpringJUnitConfig(SpringConfig.class)
public class SampleTest {
#Test
void test1() {
fail();
}
}
I need to access beans from that application context inside a TestExecutionListener to do some initial setup by overriding testPlanExecutionStarted.
import org.junit.platform.launcher.TestExecutionListener;
import org.junit.platform.launcher.TestPlan;
public class TestSetupListener implements TestExecutionListener {
#Override
public void testPlanExecutionStarted(final TestPlan testPlan) {
// Get reference to applicationContext
}
}
I was wondering if there was a way to access the same ApplicationContext that will be used by my tests or is the framework not aware of the tests application context at this point?
I've tried using SpringExtension.getApplicationContext but I can't get a reference to an ExtensionContext from within the TestExecutionListener.
The best I could find was to use the following
/**
* Registered through META-INF/spring.factories file as a default Listener
* Ensures that the AUT is in a testable state
*/
public class TestSetupListener extends AbstractTestExecutionListener {
private boolean setupRequired = true;
#Autowired
<What you need autowired>
#Override
public void beforeTestClass(final TestContext testContext) throws Exception {
if (setupRequired) {
setupRequired = false;
ApplicationContext applicationContext = testContext.getApplicationContext();
applicationContext.getAutowireCapableBeanFactory().autowireBean(this);
...
}
}

spring boot tests and embedded elastic server

The documentation says that writing #SpringbootTest doesnt mean we load all the configuration
We should be able to test a slice of application at a time
I have a case where one module talks to elastic search and we spin up EmbeddedElasticsearchServer in integration tests
However I have hard time figuring out how to define #Configuration classes, how to load them in test and how to make sure that elasticsearch server spins up once for all the tests
I am not sure about the #SpringBootTest. But you can use the below template for elasticsearch Integration est with embeddedserver
v7.0.0
#ESIntegTestCase.ClusterScope(scope = ESIntegTestCase.Scope.SUITE, numDataNodes = 1, numClientNodes = 0, transportClientRatio = 0, supportsDedicatedMasters = false)
#RunWith(com.carrotsearch.randomizedtesting.RandomizedRunner.class)
public class TestWatsonBulkIT extends ESIntegTestCase {
#Override
protected boolean addMockHttpTransport() {
return false;
}
#Override
protected Settings nodeSettings(int nodeOrdinal) {
Settings.Builder builder = Settings.builder()
.put(super.nodeSettings(nodeOrdinal))
.put(NetworkModule.TRANSPORT_TYPE_KEY, Netty4Plugin.NETTY_HTTP_TRANSPORT_NAME)
.put(NetworkModule.HTTP_TYPE_KEY, Netty4Plugin.NETTY_HTTP_TRANSPORT_NAME)
.put(HttpTransportSettings.SETTING_HTTP_PORT.getKey(), 9200)
.put(HttpTransportSettings.SETTING_HTTP_HOST.getKey(),"127.0.0.1");
Settings settings = builder.build();
return settings;
}
#Before
public void setUp() throws Exception {
beforeClass();
super.setUp();
}
#Test
public void test_1(){ // your integration test code here }
}
We should be able to test a slice of application at a time
This is possible by providing classes in #SpringBootTest annotation. I usually go for this option in order to not load everything. Let's say,
Controller:
SampleController -> ConsumerService (autowired)
Test
#RunWith(SpringRunner.class)
#SpringBootTest(classes = {ConsumerService.class, SampleController.class})
public class TestSample {
#Autowired
SampleController sam;
#Test
public void testSam() {
sam.sample();
}
}
I do have bunch of services but they won't be loaded and so their dependencies autowired.
If this is not what you're looking for please enhance your question by adding some sample codes.

SpringBootTest: how to know when boot application is done

Spring boot integration test looks like this
#RunWith(SpringRunner.class)
#SpringBootTest(classes = Application)
class IntegrationTest {
static QpidRunner qpidRunner
#BeforeClass
static void init() {
qpidRunner = new QpidRunner()
qpidRunner.start()
}
#AfterClass
static void tearDown() {
qpidRunner.stop()
}
}
So, Qpid instance is run before and teared down after all tests. I want to know is there a way to check whether spring boot application is still running before calling qpidRunner.stop(). I want to stop Qpid only when I'm sure that spring app has finished its stopping.
The Spring Boot integration test can configure an ApplicationListener which listens for ContextClosedEvent. Define a nested #TestConfiguration class inside the test class to add beans to the application's primary configuration.
#TestConfiguration
static class MyConfiguration {
#Bean
public ApplicationListener<ContextClosedEvent> contextClosedEventListener() {
return event -> qpidRunner.stop();
}
}
Taking into account that ConfigurableWebApplicationContext can be injected in a SpringBootTest, adding this lines to the code solved the problem
static ConfigurableWebApplicationContext context
#Autowired
void setContext(ConfigurableWebApplicationContext context) {
AbstractDocsIntegrationTest.context = context
}
#AfterClass
static void tearDown() {
context.stop()
qpidRunner.stop()
}
Spring docs about stop method
Stop this component, typically in a synchronous fashion, such that the
component is fully stopped upon return of this method.
JUnit AfterClass annotated method must be static, therefore #Autowired workaround with setContext method.

How do I reset my database state after each unit test without making the whole test a transaction?

I'm using Spring 3.1.1.RELEASE, Hibernate 4.1.0.Final, JPA 2, JUnit 4.8.1, and HSQL 2.2.7. I want to run some JUnit tests on my service methods, and after each test, I would like any data written to the in-memory database to be rolled back. However, I do NOT want the entire test to be treated as a transaction. For example in this test
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration({ "classpath:test-context.xml" })
public class ContractServiceTest
{
…
#Autowired
private ContractService m_contractService;
#Test
public void testUpdateContract()
{
// Add the contract
m_contractService.save(m_contract);
Assert.assertNotNull(m_contract.getId());
// Update the activation date by 6 months.
final Calendar activationDate = Calendar.getInstance();
activationDate.setTime(activationDate.getTime());
activationDate.add(Calendar.MONTH, 6);
m_contract.setActivationDate(activationDate.getTime());
m_contractService.save(m_contract);
final List<Contract> foundContracts = m_contractService.findContractByOppId(m_contract.getOpportunityId());
Assert.assertEquals(foundContracts.get(0), m_contract);
} // testUpdateContract
there are three calls to the service, ("m_contractService.save", "m_contractService.save", and "m_contractService.findContractByOppId") and each is treated as a transaction, which I want. But I don't know how to reset my in-memory database to its original state after each unit test.
Let me know if I need to provide additional information.
Since you are using Hibernate, you could use the property hibernate.hbm2ddl.auto to create the database on startup every time. You would also need to force the spring context to be reloaded after each test. You can do this with the #DirtiesContext annotation.
This might add a bit extra overhead to your tests, so the other solution is to just manually delete the data from each table.
#DirtiesContext was no solution for me because the whole application context gets destroyed an must be created after each test -> Took very long.
#Before was also not a good solution for me as I have to create #Before in each integration test.
So I decided to create an TestExecutionListener which recreates the database after each test. (With Liquibase, but it also works with Flyway and normal SQL)
public class CleanupDatabaseTestExecutionListener
extends AbstractTestExecutionListener {
public final int getOrder() {
return 2001;
}
private boolean alreadyCleared = false;
#Override
public void prepareTestInstance(TestContext testContext) throws Exception {
if (!alreadyCleared) {
cleanupDatabase(testContext);
alreadyCleared = true;
} else {
alreadyCleared = true;
}
}
#Override
public void afterTestClass(TestContext testContext) throws Exception {
cleanupDatabase(testContext);
}
private void cleanupDatabase(TestContext testContext) throws LiquibaseException {
ApplicationContext app = testContext.getApplicationContext();
SpringLiquibase springLiquibase = app.getBean(SpringLiquibase.class);
springLiquibase.setDropFirst(true);
springLiquibase.afterPropertiesSet(); //The database get recreated here
}
}
To use the TestExecutionListenere I created a custom test annotation
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
#RunWith(SpringRunner.class)
#SpringBootTest(classes = OurderApp.class)
#TestExecutionListeners(mergeMode =
TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS,
listeners = {CleanupDatabaseTestExecutionListener.class}
)
public #interface OurderTest {
}
Last but not least, I can now create tests and I can be sure that the database is in a clean mode.
#RunWith(SpringRunner.class)
#OurderTest
public class ProductSaveServiceIntTest {
}
EDIT: I improved my solution a bit. I had the problem that sometime one test method destroyed my database for all upcoming tests within the test class. So I created the annotation
package com.ourder.e2e.utils;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface ClearContext {
}
and added this to the CleanupDatabaseTestExectionListener.
#Override
public void afterTestMethod(TestContext testContext) throws Exception {
if(testContext.getTestMethod().getAnnotation(ClearContext.class)!=null){
cleanupDatabase(testContext);
}
super.afterTestMethod(testContext);
}
with help of these two snippets I am now able to create tests like this:
#Test
#ClearContext
public void testWhichDirtiesDatabase() {}
If you use flyway for migrations, I use the following pattern:
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
class JUnit5Class {
#Autowired
Flyway flyway;
#BeforeAll
public void cleanUp(){
flyway.clean();
flyway.migrate();
}
}
#TestInstance allows you to make #BeforeAll non static and thus you can migrate only once per test class. If you want to reset it for each test remove the class anotation and make change #BeforeAll to #BeforeEach.
As of JUnit 5 you could also create a custom extension and access the data source from the Spring context, like so (using Kotlin):
class DatabaseCleanerExtension : AfterEachCallback {
override fun afterEach(context: ExtensionContext) {
val ds = SpringExtension.getApplicationContext(context).getBean(DataSource::class.java)
ds.connection.use { connection ->
connection.prepareStatement("DELETE FROM my_table").execute()
}
}
}
You can then register the extension as follows:
#SpringBootTest
#ExtendWith(DatabaseCleanerExtension::class)
class SpringJunitExtensionApplicationTests { .. }
Now after each test the callback is executed and you can easily annotate any test classes this applies to.
Here is also a video on settings this up.
Make a #Before method in which you delete all data from database. You are using Hibernate so you can use HQL: delete from Contract.
You can use #Transactional annotation at Junit class level from org.springframework.transaction.annotation.Transactional.
For example:
package org.test
import org.springframework.transaction.annotation.Transactional;
#Transactional
public class ArmyTest{
}
I solve the same problem using a random memory database for each test:
#DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)
#TestPropertySource(properties = {
"spring.datasource.url=jdbc:hsqldb:mem:${random.uuid}"
})
I was having same issue and solved following this
http://www.javafixing.com/2021/10/fixed-how-to-cleanup-h2-db-after-each.html?m=1
basically you can clean the DB after every test method with:
#Sql(scripts = "clean_file.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
on clean_file.sql you can add all the SQL statements to reset the db

Resources