SpringBootTest: how to know when boot application is done - spring

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.

Related

#PostConstruct method from service class, is not being implemented while running JUnit cases

In my MQ application, the flow of the program is Route class -> Service class -> DAO class. In the service class, I have used an objectMapper to register time module in an init method annotated with #PostConstruct as follows :
#PostConstruct
public void init() {
mapper.registerModule(new JavaTimeModule())
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
}
But while testing the service class with JUnit 5, I have mocked the DAO class and used #InjectMocks for the service class. But, the registration of time module which I had done in the method annotated with #PostConstruct is not loaded to the test class.
I tried two ways :
Using #PostConstruct in test class for registering time module.
Registering time module in #BeforeEach method.
Both don't seem to work. Is there a way to bring in the time registration in service class to the JUnit or a way to implement the #PostConstruct from service class to the test class?
You must write or Integration Test with #SpringBootTest annotation or will try use #JsonTest
Junit platform by default not implemented Spring beans lifecycle manager and for correctly testing SpringBoot i recommend use Integration Test.
#SpringBootTest
class MyTest {
#Test
void foo() {}
}
#SpringBootApplication
#ComponentScan(lazyInit = true) //make test runing process faster
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
}

How do I setup baggage fields in Spring Cloud Sleuth for a Command Line Runner?

I'm successfully using Spring Cloud Sleuth in a Spring Boot microservice and am having fields logged and sent over http headers appropriately.
I now need to integrate this same process for logging and header propagation in a Spring Boot command line runner application but it looks like no trace and span are automatically setup since it isn't in the middle of an Http request (as it is a command line app). I cannot see these fields in my logs (with the same %X format in log configuration).
I've looked at the docs and can't find any examples for this specific use case. Is this possible in a command line runner app?
In order to add baggage you need to have a span. Spring Cloud Sleuth and Spring Boot create a span for you when the controller is invoked. If you want to do the same using CLI application, you need to create span yourself.
You have two options.
Using API calls:
Span span = this.tracer.nextSpan().name("mySpan");
// do some work
span.end(); // best to put it in finally to make sure span is always ended
Or you can use annotations:
#NewSpan
public void doWork() {
}
If you use the annotation, please keep in mind the AOP proxies limitations. In particular self invocations (calls using this) would not work.
#SpringBootApplication
public class ConsoleApplication
implements CommandLineRunner {
#Override
public void run(String... args) {
doWork(); //this is the same as this.doWork();
}
#NewSpan
public void doWork() {
}
}
This is not going to work as doWork is not invoked through the AOP proxy. Make sure that you annotate a component managed by Spring and then use an injected instance.
#SpringBootApplication
public class ConsoleApplication
implements CommandLineRunner {
#Autowired
private MyService myService;
#Override
public void run(String... args) {
myService.doWork();
}
}
#Component
class MyService {
#NewSpan
public void doWork() {
}
}
In this case myService is not instance of MyService, but rather an instrumented proxy.

Testing ApplicationListener with #SQL

We have a spring bean doing some initialization stuff at bootup of a spring-boot application. To do so, the application registers a ApplicationListener<ApplicationReadyEvent>, which queries the database and acts upon the data.
To test this process, we have to inject testdata in the database. Our first try was to init the database with a test-specific ApplicationListener<ApplicationStartedEvent>. This works, however, the code looks rather nasty. The idea was to use the #Sql annotation instead to load the initial data.
This works not as expected, as the data is injected after the ApplicationReadyEvent has been published. I was unable to find means to change the phase during which the #Sql data is written to the database.
Is there a way to ensure, the data of #Sql is written prior to publishing the ApplicationReadyEvent? The test is currently otherwise annotated to run with SpringRunner, #DataJpaTest and with #DirtiesContext.
Edit: Provide Code
The ApplicationListener ist provided as this:
#Component
public class ApplicationStartup implements ApplicationListener<ApplicationReadyEvent> {
#Override
public void onApplicationEvent(final ApplicationReadyEvent event) {
// Do someting with SQL-Data
}
}
While the test looks like this:
#RunWith(SpringRunner.class)
#ComponentScan
#Import(SomeTestConfig.class)
#DataJpaTest
//#SQL("/somedata.sql")
public class SomeTest {
#Test
public void test() {
// Assert ApplicationListener has run
}
}
With the test-config as follows:
#TestConfiguration
#Profile(ReplayTest.PROFILE_SOME_TEST)
class SomeTestConfig {
#Bean
public ApplicationListener<ApplicationStartedEvent> testSetupBean() {
return new ApplicationListener<ApplicationStartedEvent>() {
// Insert data within onApplicationEvent-Method
};
}
}
If I uncomment #SQL and comment-out the #Import, the testdata is visible from within the test itself but not from within the ApplicationListener.
You can make use of the #Order annotation. You can annotate your application listeners and your test with it. By default, this has lowest precedence. So you should provide higher precedence to your application listener and lowest to your test.
Listener:
#Override
#Order(Ordered.HIGHEST_PRECEDENCE)
public void onApplicationEvent(ApplicationReadyEvent event) {
System.out.println("...publishing application event");
}
Test:
#Test
#Sql(scripts = "test.sql")
#Order
public void test() {
System.out.println("...loading data");
}

Spring Boot Integration Test Inject Controller Dependencies

I am trying to write an integration test using Spring Boot that tests the transaction logic in one of my controllers.
What the test should do, is the following:
Inject one of my controllers using #Inject
Replace an email dependency in the controllers dependencies with a Mock, to avoid actually sending an email during the integration test.
Call a method of the controller
Assert that the transactions of the called method are properly rolled back when the mail sending mock throws an exception.
Now my problem is that, when the test runs, the controller is injected into my test class but all its dependencies are null. Here is my integration test:
#RunWith(SpringJUnit4ClassRunner.class)
#IntegrationTest
#SpringApplicationConfiguration(App.class)
#WebIntegrationTest
public MyIntegrationTest () {
#Inject MyController controller;
#Before
public void before () {
// replace one particular dependency of controller with a mock
}
#Test
public void testFoo () { ... }
}
Due to the test being an integration test which starts up a full spring web application context, I was expecting that my controller would have all its dependencies already autowired, but that is obviously not the case and instead all dependencies are set to null.
Question: Do I need to use some additional annotations, or setup something in my #Before method? Or am I approaching the problem from a completely wrong side?
Update: Is it possible to test my Spring MVC Layer, without testing via HTTP such as with TestRestTemplate or MockMvc? But by directly
Test with TestRestTemplate instead of injecting the controller itself. Controllers is obviously a spring bean but if you directly inject it in your test class, it wont be able to initialize the context.
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = ExampleStart.class)
public class ExampleTest {
#Autowired
private TestRestTemplate restTemplate;
#Test
public void exampleTest() {
String body = this.restTemplate.getForObject("/", String.class);
assertThat(body).isEqualTo("Hello World");
}
}
ExampleStart.java -> The spring boot starter class
#Configuration
#ComponentScan
#EnableAutoConfiguration
public class ExampleStart extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(ExampleStart.class);
}
public static void main(String[] args) {
SpringApplication.run(ExampleStart.class, args);
}
}
Ref : https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html
But if you want to test service method, you can use #Autowired and call the methods as usual.

Spring test #ContextConfiguration and static context

I have the following piece of code for my abstract test class (I know XmlBeanFactory with ClassPathResource is deprecated, but it's unlikely to be the case of the problem).
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration
public abstract class AbstractIntegrationTest {
/** Spring context. */
protected static final BeanFactory context = new XmlBeanFactory(new ClassPathResource(
"com/.../AbstractIntegrationTest-context.xml"));
...
}
It loads the default test configuration XML file AbstractIntegrationTest-context.xml (and then I use autowiring). I also need to use Spring in static methods annotated with #BeforeClass and #AfterClass, so I have a separate context variable pointing to the same location. But the thing is that this is a separate context, which will have different instances of beans. So how can I merge these contexts or how can I invoke Spring's bean initialization defined by #ContextConfiguration from my static context?
I have in mind a possible solution by getting rid of those static members, but I'm curious, if I can do it with relatively small changes to the code.
You are right, your code will produce two application contexts: one will be started, cached and maintained for you by #ContextConfiguration annotation. The second context you create yourself. It doesn't make much sense to have both.
Unfortunately JUnit is not very well suited for integration tests - mainly because you cannot have before class and after class non-static methods. I see two choices for you:
switch to testng - I know it's a big step
encode your setup/tear down logic in a Spring bean included in the context only during tests - but then it will run only once, before all tests.
There are also less elegant approaches. You can use static variable and inject context to it:
private static ApplicationContext context;
#AfterClass
public static afterClass() {
//here context is accessible
}
#Autowired
public void setApplicationContext(ApplicationContext applicationContext) {
context = applicationContext;
}
Or you can annotate your test class with #DirtiesContext and do the cleanup in some test bean:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration
#DirtiesContext(classMode = AFTER_CLASS)
public abstract class AbstractIntegrationTest {
//...
}
public class OnlyForTestsBean {
#PreDestroy
public void willBeCalledAfterEachTestClassDuringShutdown() {
//..
}
}
Not sure whether you chose any approach here, but I encounter the same problem and solved it another way using Spring test framework's TestExecutionListener.
There are beforeTestClass and afterTestClass, so both equivalent to #BeforeClass and #AfterClass in JUnit.
The way I do it:
#RunWith(SpringJUnit4ClassRunner.class)
#TestExecutionListeners(Cleanup.class)
#ContextConfiguration(locations = { "/integrationtest/rest_test_app_ctx.xml" })
public abstract class AbstractIntegrationTest {
// Start server for integration test.
}
You need to create a class that extends AbstractTestExecutionListener:
public class Cleanup extends AbstractTestExecutionListener
{
#Override
public void afterTestClass(TestContext testContext) throws Exception
{
System.out.println("cleaning up now");
DomainService domainService=(DomainService)testContext.getApplicationContext().getBean("domainService");
domainService.delete();
}
}
By doing this, you have access to the application context and do your setup/teardown here with spring beans.
Hopefully this help anyone trying to do integration test like me using JUnit + Spring.

Resources