Using properties files in spring-boot tests - java

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);
}
}

Related

JUnit test for ApplicationReadyEvent in spring boot app

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
}
}
}

Spring Qualifier without if-else

I use Spring Framework. I want to make service for create an archive. I have two types of archive. I would like to know another way how to do it not using if-else statement.
Interface
public interface Archive {
public String makeArchive();
}
Implements 1
#Component
public class ArchiveRAR implements Archive {
#Override
public String makeArchive() {
return "I made archive RAR.";
}
}
Implements 2
#Component
public class ArchiveZIP implements Archive {
#Override
public String makeArchive() {
return "I made archive ZIP.";
}
}
Service
#Service
public class RunnerApp implements CommandLineRunner {
#Autowired
#Qualifier("archiveRAR")
Archive archiveRAR;
#Autowired
#Qualifier("archiveZIP")
Archive archiveZIP;
#Override
public void run(String... strings) throws Exception {
Report report = new Report("ZIP");
//HERE
if ("RAR".equals(report.getType())) {
System.out.println(archiveRAR.makeArchive());
} else if ("ZIP".equals(report.getType())) {
System.out.println(archiveZIP.makeArchive());
}
}
}
MainApp
#SpringBootApplication
public class MainApp {
public static void main(String[] args) throws Exception {
SpringApplication.run(MainApp.class, args);
}
}
Thank you.
I suggest you to use here factory pattern instead of if-else statement:
#Component
public class ArchiveFactory {
#Autowired
private Map<String, Archive> archives;
public Archive getArhive(String archiveType) {
return archives.get(archiveType);
}
}
By this way your main method will be:
#Service
public class RunnerApp implements CommandLineRunner {
#Autowired
private ArchiveFactory archiveFactory;
#Override
public void run(String... strings) throws Exception {
Report report = new Report("ZIP");
Archive archive = archiveFactory.get(report.getType());
System.out.println(archive.makeArchive());
}
}
Of course you need also to define bean of type Map<String, Archive>.
One other way..
#Component("RAR")
public class ArchiveRAR implements Archive{...}
#Component("ZIP")
public class ArchiveZIP implements Archive {...}
And the service class to inject BeanFactory
#Service
public class RunnerApp implements CommandLineRunner {
#Autowired
BeanFactory beans;
#Override
public void run(String... strings) throws Exception {
String archiveType = "ZIP/RAR";
Archive archive = beans.getBean(archiveType,Archive.class);
archive.makeArchive();
}
There is a cleaner way to do it without basically if-else and String comparison if you can decide beforehand which one you would like to use: just pass the appropriate Archive reference to the Report bean when you create it and also define a method that will generate your archive:
#Component
class Report {
private Archive archive;
// declare that Archive dependency is required
#Autowired
public Report(#Qualifier("archiveRar") Archive archive) {
// or public Report(#Qualifier("archiveZip") Archive archive) {
// depending on which one you would like to inject and use
this.archive = archive;
}
public void generateArchive() {
archive.makeArchive();
}
}
Also annotate your 2 Archive implementations with meaningful qualifier names:
#Component
#Qualifier("archiveRar")
public class ArchiveRAR implements Archive {
#Override
public String makeArchive() {
return "I made archive RAR.";
}
}
and
#Component
#Qualifier("archiveZip")
public class ArchiveZIP implements Archive {
#Override
public String makeArchive() {
return "I made archive ZIP.";
}
}
The Service will now look simpler:
#Service
public class RunnerApp implements CommandLineRunner {
#Autowired
private Report report;
#Override
public void run(String... strings) throws Exception {
report.generateArchive();
}
}
If you need run-time injection, you can get rid of the if-else but not String value qualifier for the bean, for example by a accessing the Spring bean container using BeanFactory.
Keep the above code for Archive and its implementations and delete the Report class which will no longer be used. In the Service use this:
#Service
public class RunnerApp implements CommandLineRunner {
#Autowired
private BeanFactory beanFactory;
#Override
public void run(String... strings) throws Exception {
String qualifierValue = "archiveRar";
// or String qualifierValue = "archiveZip";
Archive archive = beanFactory.getBean(qualifierValue, Archive.class);
System.out.println(archive.makeArchive());
}
}

Spring Boot run controller without request

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

Clean code - Where should #Autowired be applied?

I'll start with a simple example. You have a Spring boot application that runs a CommandLineRunner class on initialization.
// MyCommandLineRunner.java
public class MyCommandLineRunner implements CommandLineRunner {
private final Log logger = LogFactory.getLog(getClass());
#Autowired //IntelliJ Warning
private DataSource ds;
#Override
public void run(String... args) throws Exception {
logger.info("DataSource: " + ds.toString());
}
}
// Application.java
#SpringBootApplication
public class Application {
public static void main(String... args) {
SpringApplication.run(Application.class, args);
}
#Bean
public MyCommandLineRunner schedulerRunner() {
return new MyCommandLineRunner();
}
}
Now, like this, this works, everything is OK. However, IntelliJ reports a warning where #Autowired is located (I marked where in the comment)
Spring team recommends: Always use constructor based dependency injection in your beans. Always use assertions for mandatory dependencies.
Now if I follow this, I have a constructor based dependency injection
#Autowired
public MyCommandLineRunner(DataSource ds) { ... }
This also means that I have to edit Application.java as well, since the constructor needs an argument. In Application.java if I try to use the setter injection, I'll get the same warning. If I refactor that as well, I'll end up with some, in my opinion, nasty code.
// MyCommandLineRunner.java
public class MyCommandLineRunner implements CommandLineRunner {
private final Log logger = LogFactory.getLog(getClass());
private DataSource ds;
#Autowired // Note that this line is practically useless now, since we're getting this value as a parameter from Application.java anyway.
public MyCommandLineRunner(DataSource ds) { this.ds = ds; }
#Override
public void run(String... args) throws Exception {
logger.info("DataSource: " + ds.toString());
}
}
// Application.java
#SpringBootApplication
public class Application {
private DataSource ds;
#Autowired
public Application(DataSource ds) { this.ds = ds; }
public static void main(String... args) {
SpringApplication.run(Application.class, args);
}
#Bean
public MyCommandLineRunner schedulerRunner() {
return new MyCommandLineRunner(ds);
}
}
The above code yields the same result, but doesn't report any warnings in IntelliJ.
I'm confused, how is the 2nd code better than the first one? Am I following an incorrect logic? Should this be wired differently?
In short, what's the correct way to do this?
note DataSource is just a pure example, this question applies to anything being autowired.
note 2 Just to say that MyCommandLineRunner.java can't have another, empty, constructor, since DataSource needs to be autowired/initialized. It will report an error and will not be compiled.
There are several ways to improve it.
You can remove #Autowired from your MyCommandLineRunner as you are letting a #Bean method construct an instance of it. Inject the DataSource directly into the method as an argument.
Or remove #Autowired and remove the #Bean and slap a #Component annotation on your MyCommandLineRunner to have it detected and remove factory method.
Inline your MyCommandLineRunner inside your #Bean method as a lambda.
No Autowiring in the MyCommandLineRunner
public class MyCommandLineRunner implements CommandLineRunner {
private final Log logger = LogFactory.getLog(getClass());
private final DataSource ds;
public MyCommandLineRunner(DataSource ds) { this.ds = ds; }
#Override
public void run(String... args) throws Exception {
logger.info("DataSource: " + ds.toString());
}
}
And the application class.
#SpringBootApplication
public class Application {
public static void main(String... args) {
SpringApplication.run(Application.class, args);
}
#Bean
public MyCommandLineRunner schedulerRunner(DataSource ds) {
return new MyCommandLineRunner(ds);
}
}
Usage of #Component
#Component
public class MyCommandLineRunner implements CommandLineRunner {
private final Log logger = LogFactory.getLog(getClass());
private final DataSource ds;
public MyCommandLineRunner(DataSource ds) { this.ds = ds; }
#Override
public void run(String... args) throws Exception {
logger.info("DataSource: " + ds.toString());
}
}
And the application class.
#SpringBootApplication
public class Application {
public static void main(String... args) {
SpringApplication.run(Application.class, args);
}
}
Inline CommandLineRunner
#SpringBootApplication
public class Application {
private static final Logger logger = LoggerFactory.getLogger(Application.class)
public static void main(String... args) {
SpringApplication.run(Application.class, args);
}
#Bean
public MyCommandLineRunner schedulerRunner(DataSource ds) {
return (args) -> (logger.info("DataSource: {}", ds);
}
}
All of these are valid ways of constructing your instances. Which one to use, use the one that you feel comfortable with. There are more options (all variations on the ones mentioned here).
Consider making the field ds final, then you don't need #Autowired. See more about dependency injection http://docs.spring.io/spring-boot/docs/current/reference/html/using-boot-spring-beans-and-dependency-injection.html#using-boot-spring-beans-and-dependency-injection
To keep the code clean, have you considered using Lombok annotations? #RequiredArgsConstructor(onConstructor = #__(#Autowired))
would generate the constructor with #Autowired annotations. See more here
https://projectlombok.org/features/Constructor.html
Your code could look like this:
#Slf4j
#RequiredArgsConstructor
// MyCommandLineRunner.java
public class MyCommandLineRunner implements CommandLineRunner {
//final fields are included in the constructor generated by Lombok
private final DataSource ds;
#Override
public void run(String... args) throws Exception {
log.info("DataSource: {} ", ds.toString());
}
}
// Application.java
#SpringBootApplication
#RequiredArgsConstructor(onConstructor_={#Autowired}) // from JDK 8
// #RequiredArgsConstructor(onConstructor = #__(#Autowired)) // up to JDK 7
public class Application {
private final Datasource ds;
public static void main(String... args) {
SpringApplication.run(Application.class, args);
}
#Bean
public MyCommandLineRunner schedulerRunner() {
return new MyCommandLineRunner(ds);
}
}
Later edit
Solution without Lombok relies on Spring to inject dependency when the bean is created
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
/**
* dependency ds is injected by Spring
*/
public MyCommandLineRunner schedulerRunner(DataSource ds) {
return new MyCommandLineRunner(ds);
}
}
// MyCommandLineRunner.java
public class MyCommandLineRunner implements CommandLineRunner {
private final Log logger = LogFactory.getLog(getClass());
private final DataSource ds;
public MyCommandLineRunner(DataSource ds){
this.ds = ds;
}
#Override
public void run(String... args) throws Exception {
logger.info("DataSource: "+ ds.toString());
}
}

Spring Boot Data MongoDB - Repository is null

I want to develop a little test application using Spring Boot and Spring Data MongoDB. So, in this case, I use default configuration (like localhost:27017/test database) and I try to follow the spring guide (https://spring.io/guides/gs/accessing-data-mongodb/).
I launch my application like this:
#SpringBootApplication
public class Application implements CommandLineRunner {
private static final Logger LOGGER = LoggerFactory.getLogger(Application.class);
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Override
public void run(String... strings) throws Exception {
(new DummyClass()).load();
}
}
The DummyClass is as following:
#org.springframework.stereotype.Component
#ConfigurationProperties(prefix="dummy")
public class DummyClass {
private static String url;
private List<Project> projects;
#Autowired
private ProjectRepository projectRepository;
public void setUrl(String url) {
DummyClass.url = url;
}
#Override
public void load() {
// (...) creating some project objects
projectRepository.deleteAll();
projectRepository.save(this.projects);
}
}
When the projectRepository.deleteAll() statement is executed, I receive un NullPointerException.
For information, below the ProjectRepository interface:
public interface ProjectRepository extends MongoRepository<Project, String>
{
}
And my package structure is:
com.test.dummy
Application.java
com.test.dummy.components
DummyClass.java
com.test.dummy.domain
Project.java
com.test.dummy.repositories
ProjectRepository.java
Can you help me to understand my error?
Note: I use Spring Boot 1.4.1 and Mongo 3.2
Inside your Application.run() method you instantiate your DummyClass outside of the Spring context (new DummyClass()). This way the
#Autowired
private ProjectRepository projectRepository;
is not instantiated correctly.
You should inject your DummyClass via Spring and not create a new instance of it via its constructor.
Example:
#SpringBootApplication
public class Application implements CommandLineRunner {
private static final Logger LOGGER = LoggerFactory.getLogger(Application.class);
#Autowired
DummyClass dummyClass;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Override
public void run(String... strings) throws Exception {
dummyClass.load();
}
}

Categories