I tried to write test for my spring boot application.
My application has business logic, which start after spring app has been initialized.
There is need to test triggeration of method with annotation #EventListener(ApplicationReadyEvent.class)
Below is a simple example that doesn't work. I expect that inscription "Testing..." appears in the console.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {
MySpringBootTest.MyTestConfig.class
})
public class MySpringBootTest {
#Test
public void test() {
}
#Configuration
public static class MyTestConfig {
#EventListener(ApplicationReadyEvent.class)
public void init() {
System.out.println("Testing...");
}
}
}
how do I make this example work?
Because you are using #ContextConfiguration Spring application Context is partially loaded and you don't have access to every capability of Spring application Context, however There are many ways to achieve something you want to do. One of them is using TextExecutionListener. I will show you how to use that in both Junit 4 and Junit 5 (jupiter):
Junit 5 (jupiter):
#ExtendWith(SpringExtension.class)
#ContextConfiguration(classes= MySpringBootTest.MyTestConfig.class)
#TestExecutionListeners(listeners = {MySpringBootTest.MyTestConfig.class})
public class MySpringBootTest {
#Test
public void test1() {
....
}
#Test
public void test2() {
...
}
#Configuration
public static class MyTestConfig extends AbstractTestExecutionListener {
#Override
public void beforeTestClass(TestContext testContext) throws Exception {
System.out.println("Testing...");
testContext.getApplicationContext(); //Do anything you want here
}
}
}
Here is a quote from Java doc for beforeTestClass method of TestExecutionListener:
Pre-processes a test class before execution of all tests within
the class. This method should be called immediately before
framework-specific before class lifecycle callbacks.
From the testContext that will be passed to this method you will have access to the application context and test class itself, you can do every thing you want with the test class there.
Junit 4:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {MySpringBootTest.MyTestConfig.class})
#TestExecutionListeners(listeners = {MySpringBootTest.CustomTestExecutionListener.class,
SpringBootDependencyInjectionTestExecutionListener.class})
public class MySpringBootTest {
#Test
public void test1() {
....
}
#Test
public void test2() {
...
}
#Configuration
public static class MyTestConfig {
}
public static class CustomTestExecutionListener extends AbstractTestExecutionListener {
#Override
public void beforeTestClass(TestContext testContext) throws Exception {
System.out.println("Testing...");
testContext.getApplicationContext(); //Do anything you want here
}
}
}
Related
Is there a way to run a controller for initializing some data before Spring Boot starts the Tomcat?
My current code looks like that:
#SpringBootApplication
public class Application {
public static void main(String[] args) {
AbstractApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
Controller controller = (Controller) context.getBean("controller");
controller.start();
context.close();
SpringApplication.run(Application.class, args);
}
}
#Controller
#Component("controller")
public class Controller {
#Autowired
private Runner runner;
public void start() {
runner.test();
}
}
#Configuration
#PropertySource("classpath:config.properties")
#Component("runner")
public class Runner {
#Value("${name}")
private String name;
public void test() {
System.out.println("hello " + name)
}
public String getName() {
return name;
}
}
#Controller
public class HelloController {
#Autowired
private Runner runner;
#RequestMapping("/hello")
public CalenderCollection data(#PathVariable("name")String name, Model model) {
model.addAttribute("name", runner.getName());
return "hello";
}
}
#Configuration
#ComponentScan(basePackages = "com.test")
public class AppConfig {
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
This prints the correct name into the console. But when I visit the url then runner is null. Then I thought about to change the Application and Controller class to this:
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
#Controller
#Component("controller")
public class Controller {
#Autowired
private Runner runner;
public Controller() {
runner.test();
}
}
But now I have the problem that runner is null right in the beginning. What would be a right way to collect some data in the beginning and then continue with the process?
Usually you use ApplicationRunner or CommandLineRunner to run some code before application started.
From documentation
If you need to run some specific code once the SpringApplication has
started, you can implement the ApplicationRunner or CommandLineRunner
interfaces. Both interfaces work in the same way and offer a single
run method which will be called just before SpringApplication.run(…)
completes.
The CommandLineRunner interfaces provides access to application
arguments as a simple string array, whereas the ApplicationRunner uses
the ApplicationArguments interface discussed above.
import org.springframework.boot.*
import org.springframework.stereotype.*
#Component
public class MyBean implements CommandLineRunner {
public void run(String... args) {
// Do something...
}
}
If you are just trying to run some code when the application has started, then there is no need to use Controller. Spring provides various application lifecycle hooks for such use cases.
The code would most likely look like this
#Component
public class MyListener
implements ApplicationListener<ContextRefreshedEvent> {
#Autowired
private Runner runner;
public void onApplicationEvent(ContextRefreshedEvent event) {
runner.test();
}
}
Check out this blog post and this part of the documentation for more information
If you want to initialize some values you can do something like this :
#SpringBootApplication
public class Application implements CommandLineRunner {
#Override
public void run( String... args ) throws Exception {
//initialise your value here
}
}
If this is something that affects only that class you can use
#PostConstruct
on a method of your controller class.
If this is something that is linked to the whole application, you should consider
creating an application listener on ApplicationReadyEvent
I have simple spring boot web service, where for configuration I use .properties files. As example for spring-mail configuration, I have separate file mailing.properties located in src/main/resources/config/ folder.
in main application I include it using:
#PropertySource(value = { "config/mailing.properties" })
The problem appears when it comes to tests, I would like to use the same properties from this file, but when i try to use it, I get fileNotFaundExeption.
Question is:
Should I have separate resources in my src/test folder, or it is possible to access resources from src/main folder, if yes, how?
UPDATE added sources
test class:
#RunWith(SpringRunner.class)
#SpringBootTest
#TestPropertySource("classpath:config/mailing.properties")
public class DemoApplicationTests {
#Autowired
private TestService testService;
#Test
public void contextLoads() {
testService.printing();
}
}
service class:
#Service
public class TestService
{
#Value("${str.pt}")
private int pt;
public void printing()
{
System.out.println(pt);
}
}
main app class:
#SpringBootApplication
#PropertySource(value = { "config/mailing.properties" })
public class DemoApplication {
public static void main(String[] args)
{
SpringApplication.run(DemoApplication.class, args);
}
}
You can use #TestPropertySource annotation in your test class.
For example you have this attribute in your mailing.properties file:
mailFrom=fromMe#mail.com
Just annotate #TestPropertySource("classpath:config/mailing.properties") on your test class.
You should be able to read out the property for example with the #Value annotation.
#Value("${fromMail}")
private String fromMail;
To avoid annotate this annotation on multiple test classes you can implement a superclass or meta-annotations.
EDIT1:
#SpringBootApplication
#PropertySource("classpath:config/mailing.properties")
public class DemoApplication implements CommandLineRunner {
#Autowired
private MailService mailService;
public static void main(String[] args) throws Exception {
SpringApplication.run(DemoApplication.class, args);
}
#Override
public void run(String... arg0) throws Exception {
String s = mailService.getMailFrom();
System.out.println(s);
}
MailService:
#Service
public class MailService {
#Value("${mailFrom}")
private String mailFrom;
public String getMailFrom() {
return mailFrom;
}
public void setMailFrom(String mailFrom) {
this.mailFrom = mailFrom;
}
}
DemoTestFile:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = DemoApplication.class)
#TestPropertySource("classpath:config/mailing.properties")
public class DemoApplicationTests {
#Autowired
MailService mailService;
#Test
public void contextLoads() {
String s = mailService.getMailFrom();
System.out.println(s);
}
}
I'm trying to implement a simple Spring AOP (v4) example using #Before advice with an in-place pointcut expression, but the aspect method is not invoked. I have all the required dependencies(spring-aop, aopalliance, aspectweaver). What am I doing wrong?
package com.xyz;
public class TestClass {
#PostConstruct
public void init() {
test();
}
public void test() {
...
}
}
The aspect:
#Aspect
#Component
public class MyAspect{
#Before("execution(* com.xyz.TestClass.test())")
public void beforeTest() {
...
}
}
The reason why AOP isn't executed is because the TestClass.test() is not invoked in spring context but has simple / plain invocation from TestClass.init().
To test your setup modify it to something similar to below so that the TestClass.test() invocation is managed by spring
package com.xyz;
public class TestClass {
public void test() {
...
}
}
Inject the TestClass into another class say AnotherTestClass and invoke test method from there
package com.xyz;
public class AnotherTestClass {
#Autowired
private TestClass testClass;
public void anotherTest() {
testClass.test();
}
}
Is it possible to place the setup/teardown methods using JUnit framework in a single class (which would be my baseclass) so on test runs they methods are always called first/last? it would be in a similar way to which nunit tests can be structured
currently the only way I can get my tests to kick off is if I have the setup/teardown methods within the same class as my tests are (which is something I wan't to avoid, to keep my test classes tidy)
example I would hope to set up;
public class baseclass
{
#Before
public void setUp
{}
#After
public void tearDown
{}
}
public class tests
{
#Test
public void test1
{
// test content here
}
}
Run this test and see the sequence of events
class Test1 {
#Before
public void setUp1() {
System.out.println("setUp1");
}
}
public class Test2 extends Test1 {
#Before
public void setUp2() {
System.out.println("setUp2");
}
#Test
public void test() {
System.out.println("test");
}
}
Yes, as long as your test class extend your baseclass.
For instance:
Suite
#RunWith(Suite.class)
#SuiteClasses(Tests.class)
public class AllTests {
}
BaseClass
public class BaseClass {
#BeforeClass
public static void beforeAll() {
}
#Before
public void setUp() {
}
#After
public void tearDown {
}
#AfterClass
public static void afterAll() {
}
}
Tests
public class Test extends BaseClass {
#Test
public void test1() {
}
#Test
public void test2() {
}
}
For example:
#RunWith(MockitoJUnitRunner.class)
public class ClientFormServiceTest {
#Mock
ClientFormService clientFormService;
public class GetNewClientFormTest {
#Mock
protected ClientForm result;
#Before
public void given() {
result = clientFormService.getNewForm();
}
#Test
public void should_do_something() {
}
}
public class CreateClientFormTest {
#Mock
protected ClientForm clientForm;
#Before
public void given() {
clientFormService.createForm(clientForm);
}
#Test
public void should_do_something() {
}
}
}
This is what I want to do but I can't run the unit tests if are nested to a class.
I'm the author of a JUnit TestRunner, junit-nested, which I believe may do what you want: https://github.com/avh4/junit-nested
However, from your example it's not clear why you need nested tests. The typical reason to use them is to share setup behavior, but you should consider if having separate test classes is more appropriate.
In any case, here's how you can do it with junit-nested: (Since Nested is a test runner, you'll have to use MockitoAnnotations.initMocks() instead of the Mockito test runner.)
import net.avh4.test.junit.Nested;
#RunWith(Nested.class)
public class ClientFormServiceTest {
#Mock
ClientFormService clientFormService;
#Before
public void given() {
MockitoAnnotations.initMocks(this);
}
public class GetNewClientFormTest {
#Mock
protected ClientForm result;
#Before
public void given() {
MockitoAnnotations.initMocks(this);
result = clientFormService.getNewForm();
}
#Test
public void should_do_something() {
}
}
public class CreateClientFormTest {
#Mock
protected ClientForm clientForm;
#Before
public void given() {
MockitoAnnotations.initMocks(this);
clientFormService.createForm(clientForm);
}
#Test
public void should_do_something() {
}
}
}
Why would you like to do that? If you mean to benefit from code reuse among many similar tests, you could come up with a base test class with common code and make test classes extend it.