How to do unit test for spring boot auto configuration - spring

In a spring boot auto configuration project there are two emailSender child classes: MockEmailSender and TextEmailSender. And in auto configuration only one mailSender should be created:
#Bean
#ConditionalOnMissingBean(MailSender.class)
#ConditionalOnProperty(name="spring.mail.host", havingValue="foo", matchIfMissing=true)
public MailSender mockMailSender() {
log.info("Configuring MockMailSender");
return new MockMailSender();
}
#Bean
#ConditionalOnMissingBean(MailSender.class)
#ConditionalOnProperty("spring.mail.host")
public MailSender smtpMailSender(JavaMailSender javaMailSender) {
log.info("Configuring SmtpMailSender");
return new SmtpMailSender(javaMailSender);
}
following is my unit test code:
#SpringBootApplication
public class LemonTest implements ApplicationContextAware{
private ApplicationContext context;
public static void main(String[] args){
SpringApplication.run(LemonTest.class, args);
System.out.println("haha");
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.context = applicationContext;
}
}
#RunWith(SpringRunner.class)
#SpringBootTest
public class InitTest {
#Autowired
private MailSender mailSender;
#Test
public void test(){
assertNotNull(mailSender);
}
}
And the properties are
spring.mail.host=foo
spring.mail.port=587
spring.mail.username=alert1
spring.mail.password=123456
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
spring.mail.properties.mail.smtp.starttls.required=true
Based on my auto configuration only MockEmailSender should be initialized,
but both emailSender bean are created, so multiple bean error is thrown when running the unit test. I guess my configuration settings are not loaded by the test.
So how to include the auto configuration in the test? what would be the best practices to test auto configuration?

I've always created multiple tests using separate "profiles" to control what is loaded/set, setting the active profile on the test.
#ActiveProfiles({ "test", "multipleemail" })
Then your test would ensure the expected result (multiple email providers in context, etc).
Properties can be imported if you need separate ones. I store one in src/test specifically for my unit tests.
#PropertySource({ "classpath:sparky.properties" })

I finally solve it. Just add #Import(LemonAutoConfiguration.class) to the application.
#SpringBootApplication
#Import(LemonAutoConfiguration.class)
public class LemonTest implements ApplicationContextAware{
private ApplicationContext context;
public static void main(String[] args){
SpringApplication.run(LemonTest.class, args);
System.out.println("haha");
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.context = applicationContext;
}
}

Related

Load configuration class before BeanDefinitionRegistryPostProcessor

configuration B below is using BeanDefinitionRegistryPostProcessor to dynamically register some spring beans. However I need to access Configuration class A which is autowired inside B. A always ends up being Null which is due to the fact for BeanDefinitionRegistryPostProcessor - "All regular bean definitions will have been loaded, but no beans will have been instantiated yet"
Is there anyway i can force the configuration loading so that A is instaniated before the Configuration class B so that applicationsProperties is not always null?
A
#ConfigurationProperties(prefix = "ie.test.appname.applications")
#Configuration
#EnableAutoConfiguration
#EnableConfigurationProperties
#RefreshScope
#Data
public class ApplicationProperties {
private List<Application> applications = new ArrayList<>();
}
B
#Configuration
#Import({ ApplicationProperties.class})
#Log4j
public class ConfigurationManager {
#Autowired
private ApplicationProperties applicationProperties;
#Bean
public BeanDefinitionRegistryPostProcessor beanPostProcessor(final ConfigurableEnvironment environment) {
return new BeanDefinitionRegistryPostProcessor() {
public void postProcessBeanFactory(ConfigurableListableBeanFactory arg0) throws BeansException {
}
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanRegistry) throws BeansException {
doSomething();
}
};
}
private void doSomething() {
List<Application> storeNamesList = applicationProperties.getApplications(); // null pointer
}
Main with component scan
#SpringBootApplication
#EnableAspectJAutoProxy
#EnableScheduling
#EnableAsync
#ComponentScan("ie.test.appname")
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication .class, args);
}
}

How to access properties in BeanFactoryPostProcessor with spring boot 1.5.x

I am trying to get properties from application.yml in BeanFactoryPostProcessor with spring boot 1.5.x:
The application.yml:
prong:
nfcloan:
jackson:
json-sub-types-package:
- com.shuweicloud.starter.acc.dto.request
#ConfigurationProperties(prefix = "prong.nfcloan.jackson")
public class JacksonProperties {
private List<String> jsonSubTypesPackage;
public List<String> getJsonSubTypesPackage() {
return jsonSubTypesPackage;
}
public void setJsonSubTypesPackage(List<String> jsonSubTypesPackage) {
this.jsonSubTypesPackage = jsonSubTypesPackage;
}
}
#Component
public class AccBeanFactoryPostProcessor implements BeanFactoryPostProcessor{
#Autowired
private JacksonProperties jacksonProperties;
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
List<String> packages = jacksonProperties.getJsonSubTypesPackage();
// do something
}
}
The main class:
#SpringBootApplication
#EnableConfigurationProperties({JacksonProperties.class})
public class AccountingApplication {
public static void main(String[] args) {
SpringApplication.run(AccountingApplication.class, args);
}
}
But the packages variable is null. How to solve it?
I found a solution:
#Component
public class AccBeanFactoryPostProcessor implements BeanFactoryPostProcessor, EnvironmentAware {
private Environment environment;
#Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
#SuppressWarnings("unchecked")
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
List<String> packages = environment.getProperty("prong.nfcloan.jackson.json-sub-types-package", List.class);
// do something
}
}
Spring boot internally uses Binder APIs to "map" the resolved properties into the #ConfigurationProperties beans.
Indeed, this resolution happens during the spring boot startup process after the BeanFactoryPostProcessors get created.
Now your solution will clearly work, because you kind of "bypass" this resolution.
However if you want to still have the Configuration as an instance of JacksonProperties (might be relevant if you have a lot of properties to resolve, or in general prefer to work more in a more spring-ish manner), you can use this binder API:
// inside the "postProcessBeanFactory" method, using the injected environment
BindResult<ExampleProperties> bindResult = Binder.get(environment)
.bind("prong.nfcloan.jackson", JacksonProperties.class);
JacksonProperties properties = bindResult.get();

Cannot correctly load application.yaml from SpringBoot/JUnit

I am a newbie with SpringBoot and I am trying to develop a first application.
My application has a configuration that is provided in an application.yaml. Currently, it successfully reads its configuration at startup.
However, if I embed my application in a Springboot/JUnit test, the application.yaml is not correctly exploited.
My impression is that, using Springboot/JUnit, application.yaml is
read as if it was an application.properties: it only accepts
parameters that are provided on a single line (e.g. thread-pool: 10)
but not on a multi-line
wordpress:
themes:
default-folder: /wp-content/themes/mkyong
I reproduced the same issue from a project I found in github: https://github.com/mkyong/spring-boot.git, in the directory yaml-simple
the application successfully reads its configuration:
#SpringBootApplication
public class Application implements CommandLineRunner {
#Autowired
private WordpressProperties wpProperties;
#Autowired
private GlobalProperties globalProperties;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Override
public void run(String... args) {
System.out.println(globalProperties);
System.out.println(wpProperties);
}
}
But if I create the following JUnit test in the directory
src/test/java/com/mkyong
#RunWith(SpringRunner.class)
#TestPropertySource(locations="classpath:application.yml")
public class MyTest {
#Autowired
private WordpressProperties wpProperties;
#Autowired
private GlobalProperties globalProperties;
#Test
public void myTest() {
Assert.assertTrue(globalProperties.getThreadPool() == 10); /// OK
Assert.assertEquals("/wp-content/themes/mkyong", wpProperties.getThemes().getDefaultFolder()); // KO
}
#SpringBootApplication
static class TestConfiguration {
}
}
while running it, the configuration is only partially read!!!
(please note that my problem does not appear using application.properties but I prefer yaml against properties)
Thanks the answer of user7294900, I found that adding the annotation #ContextConfiguration(initializers = ConfigFileApplicationContextInitializer.class) to my test class solved the problem:
#RunWith(SpringRunner.class)
#TestPropertySource(locations="classpath:application.yml")
#ContextConfiguration(initializers = ConfigFileApplicationContextInitializer.class)
public class MyTest {
#Autowired
private WordpressProperties wpProperties;
#Autowired
private GlobalProperties globalProperties;
#Test
public void myTest() {
Assert.assertTrue(globalProperties.getThreadPool() == 10);
Assert.assertEquals("/wp-content/themes/mkyong", wpProperties.getThemes().getDefaultFolder());
}
#SpringBootApplication
static class TestConfiguration {
}
}

#Autowired does not work with #Configurable

I am trying to do an image upload API. I have a ImageUpload task as follows,
#Component
#Configurable(preConstruction = true)
#Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class ImageUploadTask implements Callable<JSONObject> {
#Autowired
private ImageUploadService imageUploadService;
#Override
public JSONObject call() throws Exception {
....
//Upload image via `imageUploadService`
imageUploadService.getService().path('...').post('...'); // Getting null pointer here for imageUploadService which is a WebTarget
}
}
The ImageUploadService looks like the below,
#Component
public class ImageUploadService {
#Inject
#EndPoint(name="imageservice") //Custom annotation, battle tested and works well for all other services
private WebTarget imageservice;
public WebTarget getService() {
return imageservice;
}
}
Here is the spring boot application class,
#ComponentScan
#EnableSpringConfigured
#EnableLoadTimeWeaving(aspectjWeaving=EnableLoadTimeWeaving.AspectJWeaving.ENABLED)
#EnableAutoConfiguration
public class ImageApplication extends SpringBootServletInitializer {
#Bean
public InstrumentationLoadTimeWeaver loadTimeWeaver() throws Throwable {
InstrumentationLoadTimeWeaver loadTimeWeaver = new InstrumentationLoadTimeWeaver();
return loadTimeWeaver;
}
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
servletContext.addListener(new RequestContextListener());
}
public static void main(String[] args) throws IOException {
SpringApplication.run(ImageApplication.class);
}
}
Additional information :
Spring version of dependencies are at 4.2.5.RELEASE
pom.xml has dependencies added for spring-aspects and
spring-instrument
I am getting a NullPointerException in ImageUploadTask. My suspicion is that #Autowired doesn't work as expected.
Why wouldn't work and how do I fix this?
Is it mandatory to use #Autowired only when I use #Conigurable, why not use #Inject? (though I tried it and getting same NPE)
By default the autowiring for the #Configurable is off i.e. Autowire.NO beacuse of which the imageUploadService is null
Thus update the code to explicity enable it either as BY_NAME or BY_TYPE as below.
#Component
#Configurable(preConstruction = true, autowire = Autowire.BY_NAME)
#Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class ImageUploadTask implements Callable<JSONObject> { .... }
Rest of the configuration viz. enabling load time weaving seems fine.
Also regarding #Inject annotation have a look here which pretty much explains the difference (or similarity perhaps)

Using test application.properties file with CamelSpringTestSupport in Spring Boot

Prerequisites
Apache Tomcat 7
Spring 4.1.5.RELEASE
Spring Boot 1.2.2.RELEASE
Apache Camel 2.15.1
Problem
I am Using Spring Boot with a configuration class which is also used by EndpointSetup.
#SpringBootApplication
#Import({MyConfiguration.class, EndpointSetup.class})
public class MyFatJarRouter extends FatJarRouter { ... }
#Configuration
#ConfigurationProperties(prefix = "camel.route", ignoreUnknownFields = false)
public class MyConfiguration {
private List<String> brokerUrl = new ArrayList<>();
public List<String> getBrokerUrl() {return brokerUrl;}
public void setBrokerUrl(List<String> brokerUrl) {this.brokerUrl = brokerUrl;}
}
In production properties will be read from conf/application.properties by default.
I want to test my routes via CamelSpringTestSupport
So I have tried following:
I have placed a application.properties under test/resources/config/application.properties (--> in classpath of test)
then wrote following:
public class MyJmsTest extends CamelSpringTestSupport {
#Override
protected AbstractApplicationContext createApplicationContext() {
return new AnnotationConfigApplicationContext(MyFatJarRouter.class);
}
#Test
public void myTest() throws Exception {
...
}
}
In the example above the configuration is not read from the application.properties placed in test folder.
How can I read a test specific config file in my CamelSpringTestSupport Unit-Test?
I may be little late in answering, but there is a better way than hacking endpoints. The following solution uses toD introduced in Camel 2.16. I wrote a custom component "github" (there's an official one as well), and the following is how I test it. Note that I'm not using a single Camel proprietary annotation. To inject properties, I can either use the properties attribute in #SpringBootTest, or any of the other standard techniques available in Spring Boot.
Note that I'm using $simple{...} to avoid clash with Spring property resolution.
<rant>
And yes, Camel documentation sucks! They write it like release notes, with a section dedicated to each release, and don't seem to update the doc to keep up with the latest versions (the following technique is not documented). Imagine going to a restaurant and asking for the special, only to be told by the server about the special for the day before, and the week before, and so on. How about versioning the doc instead?
</rant>
#RunWith(CamelSpringBootRunner.class)
#SpringBootTest
#DirtiesContext(classMode = AFTER_EACH_TEST_METHOD)
public class GitHubRouteTest {
#Autowired
private CamelContext camelContext;
#Autowired
private ProducerTemplate template;
#Autowired
private GitHubClient gitHubClient;
#Test
public void testGitHubClientInvoked() throws InterruptedException {
template.sendBodyAndHeader("direct:start", "whatever",
"endpoint", "commits/test/test?username=test&password=test");
verify(gitHubClient).getCommitsForARepo(eq("test"), eq("master"), eq("test"), eq(20));
}
#SpringBootApplication
public static class TestApplication {
public static void main(String[] args) {
new SpringApplicationBuilder()
.sources(TestApplication.class)
.web(false)
.run(args);
}
#Bean
public RouteBuilder testRoute() {
return new RouteBuilder() {
#Override
public void configure() throws Exception {
from("direct:start")
.toD("github:$simple{in.header.endpoint}");
}
};
}
#Bean
public GitHubClient mockGitHubClient() {
GitHubClient mock = Mockito.mock(GitHubClient.class);
return mock;
}
}
}
I solved it by using standard spring unit-tests like this:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Application.class)
#ActiveProfiles("test") // Load applicaton-test.properties in test/resources/config/application-test.properties
#DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) // cleanup spring context because jms broker does not exit properly
public class MyJmsTest {
private static final String MOCK_MY_ENDPOINT = "mock:myEndpoint";
#Autowired
CamelContext context;
#Autowired
ApplicationContext applicationContext;
#Autowired
ProducerTemplate producerTemplate;
#Before
public void configureMocks() throws Exception {
context.getRouteDefinition("MyRoute")
.adviceWith(context, new AdviceWithRouteBuilder() {
#Override
public void configure() throws Exception {
weaveByToString(".*myEndPointId.*")
.replace()
.to(MOCK_MY_ENDPOINT);
}
});
final MockEndpoint endpoint = context.getEndpoint(MOCK_MY_ENDPOINT, MockEndpoint.class);
endpoint.whenAnyExchangeReceived(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
InputStream inStream = getClass().getClassLoader().getResourceAsStream("xml/my.xml");
String in = context.getTypeConverter().convertTo(String.class, inStream);
exchange.getIn().setBody(in);
}
});
}
#Test
public void synchronousCallBasic_1() throws Exception {
final MyConfiguration MyConfiguration = applicationContext.getBean(MyConfiguration.class);
final String myMessageBody =
context.getTypeConverter().convertTo(String.class, getClass().getClassLoader()
.getResourceAsStream("xml/0010_example.xml"));
final Object myResult = producerTemplate.requestBody(MyConfiguration.getActiveMqSynchronousEndpointUri(), myMessageBody);
assertThat(myResult, notNullValue());
assertThat((String)myResult, is("<example>1</example>"));
}
}
I solved this issue, with a lot of annotation which I found here, and now the test properties are correctly injected:
#RunWith(CamelSpringBootRunner.class)
#SpringBootTest
#ActiveProfiles("test")
#EnableAutoConfiguration
#ComponentScan
#ContextConfiguration()
public class MessageDeliveryTest{
}
Also, the test properties file needs to be named application-{env}.properties, where "env" is the profile used here. For eg. for test the properties file should be application-test.properties

Resources