How do I JUnit test a Spring autowired constructor? - spring

I have done quite a bit of searching online and I can't find an example of unit testing with an autowired constructor. I am using Spring to autowire in the values from a properties file to my application. I want to unit test MyApp.java's start method, but I have an autowired constructor so I don't know how to instantiate MyApp. Without the autowired properties, I was doing this in my unit test:
#Test
public void testStart() {
try{
MyApp myApp = new MyApp();
myApp.start();
}
catch (Exception e){
fail("Error thrown")
}
}
I don't want to mock the autowiring, as I need to obtain the values from the properties file and to further complicate things, I am configuring everything through annotations. I don't have a spring.xml, application-context.xml, or a web.xml file. So how do I go about instantiating/testing MyApp's start method? I tried adding in #RunWith(SpringJUnit4ClassRunner.class) and autowiring MyApp myApp, but it throws errors about failing to load the application context that aren't fixed by implementing ApplicationContextAware on the test class.
Here is MyApp.java
#Component
public class MyApp {
private static ApplicationContext applicationContext;
private static MyAppProperties myAppProperties;
//Obtain the values from the app.properties file
#Autowired
MyApp(MyAppProperties myAppProps){
myAppProperties = myAppProps;
}
public static void main(String[] args) throws Exception {
// Instantiate the application context for use by the other classes
applicationContext = new AnnotationConfigApplicationContext("com.my.company");
start();
}
/**
* Start the Jetty server and configure the servlets
*
* #throws Exception
*/
public static void start() throws Exception {
// Create Embedded Jetty server
jettyServer = new Server();
// Configure Jetty so that it stops at JVM shutdown phase
jettyServer.setStopAtShutdown(true);
jettyServer.setStopTimeout(7_000);
// Create a list to hold all of the handlers
final HandlerList handlerList = new HandlerList();
// Configure for Http
HttpConfiguration http_config = new HttpConfiguration();
http_config.setSecureScheme("https");
http_config.setSecurePort(myAppProperties.getHTTP_SECURE_PORT());
....
}
}
Here is my app.properties file
# Spring Configuration for My application
#properties for the embedded jetty server
http_server_port=12345
Here is MyAppProperties.java
#Component
public class MyAppProperties implements ApplicationContextAware {
private ApplicationContext applicationContext;
//List of values from the properties files to be autowired
private int HTTP_SERVER_PORT;
...
#Autowired
public MyAppProperties( #Value("${http_server_port}") int http_server_port, ...){
this.HTTP_SERVER_PORT = http_server_port;
}
/**
* #return the applicationContext
*/
public ApplicationContext getApplicationContext() {
return applicationContext;
}
/**
* #param applicationContext
* the applicationContext to set
*/
#Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
/**
* #param name
* the name to set
*/
public void setHTTP_SERVER_PORT(String name) {
JETTY_SERVER_NAME = name;
}
/**
* #return the httpServerPort
*/
public int getHTTP_SERVER_PORT() {
return HTTP_SERVER_PORT;
}
}
Here is MyAppTest.java
#RunWith(SpringJUnit4ClassRunner.class)
public class MyAppTest implements ApplicationContextAware{
private ApplicationContext applicationContext;
#Override
public void setApplicationContext(ApplicationContext appContext) {
applicationContext = appContext;
}
#Autowired
private MyApp myapp;
#Test
public void testStart(){
try {
if(myapp != null){
myapp.start();
}
else{
fail("myapp is null");
}
} catch (Exception e) {
fail("Error thrown");
e.printStackTrace();
}
}
}
UPDATE: Here is my configuration class
#Configuration
#Component
public class ApplicationConfig implements ApplicationContextAware {
private final Logger LOGGER = LoggerFactory.getLogger(ApplicationConfig.class);
private ApplicationContext applicationContext;
/**
* #return the applicationContext
*/
public ApplicationContext getApplicationContext() {
LOGGER.debug("Getting Application Context", applicationContext);
return applicationContext;
}
/**
* #param applicationContext
* the applicationContext to set
*/
#Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
// Needed for #Value
/**
* Property sources placeholder configurer.
*
* #return the property sources placeholder configurer
*/
#Bean
public PropertyPlaceholderConfigurer getPropertyPlaceholderConfigurer() {
PropertyPlaceholderConfigurer propertyPlaceholderConfigurer = new PropertyPlaceholderConfigurer();
propertyPlaceholderConfigurer.setLocation(new ClassPathResource("app.properties"));
return propertyPlaceholderConfigurer;
}
...
}

We can mock the objects using the jmockito framework.
Using #InjectMocks for dependency injection via Mockito
You also have the #InjectMocks annotation which tries to do constructor, method or field dependency injection based on the type. The following code is a slightly modified example from the Javadoc.
// Mockito can construct this class via constructor
public class ArticleManager {
ArticleManager(ArticleCalculator calculator, ArticleDatabase database) {
}
}
// Mockito can also perform method injection
public class ArticleManager {
ArticleManager() { }
void setDatabase(ArticleDatabase database) { }
void setCalculator(ArticleCalculator calculator) { }
}
// Mockito can also perform field injection
public class ArticleManager {
private ArticleDatabase database;
private ArticleCalculator calculator;
}
The following will be the unit test class.
#RunWith(MockitoJUnitRunner.class)
public class ArticleManagerTest {
#Mock private ArticleCalculator calculator;
#Mock private ArticleDatabase database;
#Spy private UserProvider userProvider = new ConsumerUserProvider();
// creates instance of ArticleManager
// and performs constructor injection on it
#InjectMocks private ArticleManager manager;
#Test public void shouldDoSomething() {
// assume that ArticleManager has a method called initialize which calls a method
// addListener with an instance of ArticleListener
manager.initialize();
// validate that addListener was called
verify(database).addListener(any(ArticleListener.class));
}
}
Make sure that you are using #RunWith(MockitoJUnitRunner.class)
For more information see http://docs.mockito.googlecode.com/hg/1.9.5/org/mockito/InjectMocks.html.

Related

Injecting Spring Dependencies into ConstrantValidator

I'm using Bean Validation. I have a custom validator #MyValidator that needs to look up a value with an injected Spring managed DAO object. How can I get access to this? Spring isn't injecting the DAO into my "MyValidator" object.
#Component
public class CodeListValidator implements ConstraintValidator<CodeList, String> {
#Autowired
private ICodeListCommonService codeListCommonService;
private CodeListEnum codeListID;
#Override
public void initialize(CodeList constraintAnnotation) {
this.codeListID = constraintAnnotation.value();
}
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return codeListCommonService.doesCodeEntityExistForCodeList(codeListID.getDbCodeListId(), value, ProviderConstants.CODE_LIST_STATUS_ACTIVE);
}
}
The "codeListCommonService" is null. This is because Spring isn't creating the class - but how can I get this to work with Spring AoP?
The use of this validator looks like this:
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
MyObject validateMe = new MyObject();
Set<ConstraintViolation<MyObject>> constraintViolations = validator.validate(validateMe);
For MyObject:
public class MyObject {
#Size(max = 1)
#CodeList(CodeListEnum.CARTYPE)
public String carType;
}
So when the validator runs, it processes the annotations... I just need to get a service injected into the CodeListValidator I made to it can do a DB lookup to verify the value against the DB list of "valid car type values".
EDIT: The solution:
Played around with the idea of making a Spring aware factory- too much integration with existing code.
The solution that seems the best (and it works here) is to make a Spring service that stores the ApplicationContext in a static method so "non-Spring managed" beans can get to them.
So a new service:
#Service
public class SpringApplicationContextService implements ISpringApplicationContextService, ApplicationContextAware {
private static ApplicationContext applicationContext;
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
}
And then any validator or non-Spring bean can get at the Spring beans via:
public class CodeListValidator implements ConstraintValidator<CodeList, String> {
#Autowired
private ICodeListCommonService codeListCommonService;
private CodeListEnum codeListID;
#Override
public void initialize(CodeList constraintAnnotation) {
this.codeListID = constraintAnnotation.value();
}
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
ICodeListCommonService codeListCommonService = SpringApplicationContextService.getApplicationContext().getBean(ICodeListCommonService.class);
return codeListCommonService.doesCodeEntityExistForCodeList(codeListID.getDbCodeListId(), value, ProviderConstants.CODE_LIST_STATUS_ACTIVE);
}
}
And, of course, the reason for all of this (which is used dozens of times in this app):
#CodeList(CodeListEnum.EMAIL_OPT_OUT_FLAG)
public String emailOptOutFlag;
#CodeList(CodeListEnum.CLEARING_HOUSE)
public String clearingHouse;
The minimum setup for #Autowired to work properly in ConstraintValidator implementation is to have this bean in a Spring #Configuration:
#Bean
public Validator defaultValidator() {
return new LocalValidatorFactoryBean();
}
This allows any beans, including ApplicationContext, to be injected directly into a ConstraintValidator:
#Constraint(validatedBy = DemoValidator.class)
public #interface DemoAnnotation {
// ...
Class<?> beanClass();
}
public class DemoValidator implements ConstraintValidator<DemoAnnotation, String> {
private final ApplicationContext applicationContext;
private Object bean;
#Autowired
public DemoValidator(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
#Override
public void initialize(DemoAnnotation constraint) {
Class<?> beanClass = constraint.beanClass();
bean = applicationContext.getBean(beanClass);
}
#Override
public boolean isValid(String obj, ConstraintValidatorContext context) {
return !obj.isEmpty();
}
}
Demo
For a really flexible validation solution I would recommend Jakub Jirutka's Bean Validator utilizing Spring Expression Language (SpEL) which allows things like:
public class Sample {
#SpELAssert("#myService.calculate(#this) > 42")
private int value;
}

Autowiring not working in springboot application

I am trying to create a Spring boot application with JFrame. I can see my beans in applicationContext but they are not getting autowired. I am unable to find the reason for this issue. Can someone help me with this?
Here is the code:
JavauiApplication - it is showing both userManager and userNameRepository is beans
#SpringBootApplication
public class JavauiApplication implements CommandLineRunner {
#Autowired
private ApplicationContext appContext;
public static void main(String[] args) {
new SpringApplicationBuilder(JavauiApplication.class).headless(false).run(args);
java.awt.EventQueue.invokeLater(() -> new InputNameForm().setVisible(true));
}
#Override
public void run(String... args) throws Exception {
String[] beans = appContext.getBeanDefinitionNames();
Arrays.sort(beans);
for (String bean : beans) {
System.out.println(bean);
}
}
}
InputNameForm.java -> userManager coming null
#Component
public class InputNameForm extends javax.swing.JFrame {
/**
* Creates new form InputNameForm
*/
public InputNameForm() {
initComponents();
}
#Autowired
UserManager userManager;
private void submitButtonActionPerformed(java.awt.event.ActionEvent evt) {
userManager.setName(firstName.getText(), lastName.getText());
}
/**
* #param args the command line arguments
*/
public static void main(String args[]) {
try {
for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
if ("Nimbus".equals(info.getName())) {
javax.swing.UIManager.setLookAndFeel(info.getClassName());
break;
}
}
} catch (ClassNotFoundException ex) {
java.util.logging.Logger.getLogger(InputNameForm.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
}
/* Create and display the form */
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new InputNameForm().setVisible(true);
}
});
}
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JTextField firstName;
private javax.swing.JLabel firstNameLabel;
private javax.swing.JTextField lastName;
private javax.swing.JLabel lastNameLabel;
private javax.swing.JButton submitButton;
// End of variables declaration//GEN-END:variables
}
UserManager.java -> userNameRepository is coming null
#Component
public class UserManager {
#Autowired
UserNameRepository userNameRepository;
public void setName(String firstName, String lastName) {
userNameRepository.save(new UserName(firstName, lastName));
System.out.println(userNameRepository.findAllByFirstName(firstName));
}
}
It's a very common problem and it occurs because newcomers don't understand how the IoC container works.
Firstly, BeanDefinitionReader reads metadata about your beans from XML, Annotations(#Component, #Service etc), JavaConfig or Groovy script.
There are several BeanPostProcessor's which is responsible for reading all of these Spring annotation you're writing(#Autowired etc).
BeanFactory creates all BeanPostProcessor's then it creates all of your beans.
What happen if you create your bean with #Autowired dependencies via new operator? Nothing, because it isn't actually a bean. The object you created isn't related to IoC container. You may have the bean already in your ApplicationContext if you marked it with #Component(for example) but the object which was created via new operator wont be processed by Spring(annotations won't work).
Hope this helps.
PS: The lifecycle is simplified.
I had the same problem few days ago. What I undertood was that GUI builders like the one that comes with netbeans will automatically create components using new keyword. This means that those components won't be manage by spring. The code usually loks like this:
private void initComponents() {
jPanel1 = new javax.swing.JPanel(); //This component will not be managed by spring.
//...
}
You could use the following class provided here, to make it work.
#Component
public class BeanProvider {
private static ApplicationContext applicationContext;
// Autowires the specified object in the spring context
public static void autowire(Object object) {
applicationContext.getAutowireCapableBeanFactory().autowireBean(object);
}
#Autowired
private void setApplicationContext(ApplicationContext applicationContext) {
BeanProvider.applicationContext = applicationContext;
}
}
The top level SwingApp class:
#SpringBootApplication
public class SwingApp implements CommandLineRunner {
public static void main(String[] args) {
new SpringApplicationBuilder(SwingApp.class)
.headless(false).bannerMode(Banner.Mode.OFF).run(args);
}
#Override
public void run(String... args) throws Exception {
SwingUtilities.invokeLater(() -> {
MainFrame frame = new MainFrame();
frame.setVisible(true);
});
}
}
The MainFrame class:
public class MainFrame extends javax.swing.JFrame {
public MainFrame() {
initComponents();
}
private void initComponents() {
//Gui Builder generated code. Bean not managed by spring.
//Thus, autowired inside CustomPanel won't work if you rely on ComponentScan.
jPanel1 = new CustomJPanel();
//...
}
private CustomJPanel jPanel1;
}
The panel class where you want to autowire things:
//#Component //not needed since it wont work with gui generated code.
public class CustomJPanel extends javax.swing.JPanel{
#Autowired
private SomeRepository someRepository
public CustomJPanel(){
BeanProvider.autowire(this); //use someRepository somewhere after this line.
}
}
I have the same problem in a JavaFx project. Service and Component annotated classes were null in UI controllers even if it was shown in context that it was created. Below code worked for me
#Component
public class FxmlLoaderWithContext {
private final ApplicationContext context;
#Autowired
public FxmlLoaderWithContext(ApplicationContext context) {
this.context = context;
FXMLLoader fxmlloader = new FXMLLoader();
fxmlloader.setControllerFactory(context::getBean); //this row ensure services and components to be autowired
}
}
I think it returns null because you using command new to create object, such as new InputNameForm(). When creating object like that, the object isn't managed by Spring. That's why autowired not working.
The solution is registering your class as a bean.
You can use a class like in here.
#Component
public class BeanProvider {
private static ApplicationContext applicationContext;
public static void autowire(Object object) {
applicationContext.getAutowireCapableBeanFactory().autowireBean(object);
}
#Autowired
private void setApplicationContext(ApplicationContext applicationContext) {
BeanProvider.applicationContext = applicationContext;
}
}
And then, in your class InputNameForm constructor, call this:
class InputNameForm() {
BeanProvider.autowire(this);
...
}
And that's it. Spring will take care the rest.

How to autowired in quartz?

Previously I had set it up to autowired in a quartz job.
Note here.
But, the autowired of the job inner class will fail.
My job code example is here.
public class MyJob extends QuartzJobBean {
#Autowired
private Hello hello; //<--- this is suceess!
#Override
public void executeInternal(JobExecutionContext context) {
//...
Do do = new Do();
do.doSomething();
//...
}
}
Do.java
public class Do {
#Autowired
priavte Do2 do2; // <---- ***this is null !***
//...
}
Why is this happening?
How do I solve it and what concepts should I know more?
Quartz jobs are not ran in the same context as spring so autowired objects become null within the same class. You have to make Quartz Spring aware.
First add sprint-context-support
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
Then create a Application Context Holder that is Application Context Aware
#Component
public final class ApplicationContextHolder extends SpringBeanJobFactory implements ApplicationContextAware {
private static ApplicationContext context;
private transient AutowireCapableBeanFactory beanFactory;
#Override
public void setApplicationContext(ApplicationContext ctx) throws BeansException {
beanFactory = ctx.getAutowireCapableBeanFactory();
context = ctx;
}
#Override
protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
final Object job = super.createJobInstance(bundle);
beanFactory.autowireBean(job);
return job;
}
public static ApplicationContext getContext() {
return context;
}
}
Then you can create your Quartz Scheduler Configuration Class
#Configuration
public class QuartzSchedulerConfiguration {
#Autowired
private ApplicationContext applicationContext;
/**
* Create the job factory bean
* #return Job factory bean
*/
#Bean
public JobFactory jobFactory() {
ApplicationContextHolder jobFactory = new ApplicationContextHolder();
jobFactory.setApplicationContext(applicationContext);
return jobFactory;
}
/**
* Create the Scheduler Factory bean
* #return scheduler factory object
*/
#Bean
public SchedulerFactoryBean schedulerFactory() {
SchedulerFactoryBean factory = new SchedulerFactoryBean();
factory.setAutoStartup(true);
factory.setSchedulerName("My Scheduler");
factory.setOverwriteExistingJobs(true);
factory.setJobFactory(jobFactory());
return factory;
}
}
Now this will place your quartz scheduler in the same context as Spring, so you can now create a SchedulerService class.
#Service
public class SchedulerService {
#Autowired
private SchedulerFactoryBean schedulerFactory;
private Scheduler scheduler;
/**
* Initialize the scheduler service
*/
#PostConstruct
private void init() {
scheduler = schedulerFactory.getScheduler();
}
}
now you can populate this class with methods to create your schedules using the scheduler object and when the task is triggered the class that extends Job will be context aware with spring and the autowired objects will no longer be null
to address the follow up question implement the ApplicationContextHolder component then autowire it into your SchedulerConfig class
#Autowire
ApplicationContextHolder holder
#Bean
// injecting SpringLiquibase to ensure liquibase is already initialized and created the quartz tables:
public JobFactory jobFactory(SpringLiquibase springLiquibase) {
AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory();
jobFactory.setApplicationContext(holder);
return jobFactory;
}

Class Cast Exception in while mocking bean

I am trying to create test cases in my application using powermock and TestNG. This is a web application and spring context xml is loaded through web.xml. I want to mock the spring context xml with my own test xml and access beans from there.
ContextLoader Code
public class AppContextLoader implements ApplicationContextAware {
private static ApplicationContext context;
public void setApplicationContext(ApplicationContext context) {
this.context = context;
}
public static Object getBean(String beanName) {
return context.getBean(beanName);
}
}
I am trying to access a bean later in my code like below
XMLConverter converter = (XMLConverter) AppContextLoader.getBean("XMLConverter");
Below is my Test Code
#PrepareForTest(AppContextLoader.class)
#PowerMockIgnore({"javax.management.*", "javax.xml.*","org.xml.*","org.w3c.dom.*"})
public class ImplTest {
Impl impl;
ApplicationContext context;
#ObjectFactory
/**
* Configure TestNG to use the PowerMock object factory.
*/
public IObjectFactory getObjectFactory() {
return new org.powermock.modules.testng.PowerMockObjectFactory();
}
#BeforeClass
public void setUp() throws Exception {
impl = new Impl();
context = new ClassPathXmlApplicationContext("Test_config.xml");
}
#Test
public void execute() {
try {
PowerMock.mockStatic(AppContextLoader.class);
expect(AppContextLoader.getBean("XMLConverter")).andReturn(context.getBean("XMLConverter"));
PowerMock.replay(AppContextLoader.class);
actualResponse = impl.execute(request, "");
//PowerMock.verify(AppContextLoader.class);
Assert.assertEquals("", actualResponse);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
When I am executing this code I am getting below error:
java.lang.ClassCastException: com.xxx.xxx.xxx.xxx.bean.XMLConverter cannot be cast to com.xxx.xxx.xxx.xxx.bean.XMLConverter
I am new to unit testing, and not sure what is causing this issue where same class are not able to cast each other. Any help will be appreciated

How to use the appicationcontextaware in java class

I need to load the applicationcontext in java class in which the applicationcontextaware bean is defined. I need to access the other beans inside the applicationcontext.xml using the applicationcontextaware. I dont want to load the context using
ClassPathXmlApplicationContext("applicationContext.xml");
I need to access the beans inside the applicationContext like this
ApplicationContextAccess.getInstance().getApplicationContext.getbean("BeanName");
Applicationcontextacess implemented as singleton class:
public class ApplicationContextAccess implements ApplicationContextAware {
private ApplicationContext applicationContext = null;
private static ApplicationContextAccess applicationContextAccess=null;
private ApplicationContextAccessor() {
}
public static synchronized ApplicationContextAccess getInstance() {
if(applicationContextAccess == null)
{
applicationContextAccess = new ApplicationContextAccess();
}
return applicationContextAccess;
}
public void ApplicationContext getApplicationContext() {
return applicationContext;
}
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
applicationContext = applicationContext;
}
}
I need to access the beans inside the applicationContext like this ApplicationContextAccess.getInstance().getApplicationContext.getbean("BeanName");
But I have a doubt how the getApplicationContext loads the applicationContext.xml........?

Resources