spring3 annotation with main method - java

I have following class:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations={"classpath:/com/home/app/Home-ctx.xml"})
public class LoginDetailsTest {
public static void main(String[] args) {
new LoginDetailsTest().testLoginDetails();
}
#Inject
#Named(HomeConstants.loginDetailsService)
private LoginDetailsService loginDetailsService;
private List<UserLogin> loginDetails;
#Test
public void testLoginDetails() {
UserLogin login = new UserLogin();
login.setLoginName("test");
login.setLoginPassword("test123");
loginDetails = loginDetailsService.loginDetails(login);
for (UserLogin loginDet : loginDetails) {
System.out.println(loginDet.getLoginName());
System.out.println(loginDet.getLoginPassword());
}
}
}
if i run above code as junit test, then it gives expected result.
If I run as Java application ie main method, then it gives null pointer exception for
loginDetailsService.loginDetails(login). how can run as main method without error?

You still need to do what JUnit does when you "run code as junit test" to bootstrap your application context and dependency injection:
public static void main(String[] args) {
org.junit.runner.JUnitCore.run(LoginDetailsTest.class);
}

The mainis a different thing. Because by instantiating the class by new LoginDetailsTest() it is not build by Spring - there can be no dependency injection.
What you need to do is:
make a new application context appctx.xml for your main method that imports Home-ctx.xml and declare a new bean <bean id="loginDetailsTest" class="LoginDetailsTest"/>
in your main method get an instance of the bean and call testLoginDetails() like this:
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("appctx.xml");
LoginDetailsTest loginDetailsTest = (LoginDetailsTest) context.getBean("loginDetailsTest");
loginDetailsTest.testLoginDetails();
}
In general you should separate the JUnit test, main method and business logic.

First things first the reason your test works is (SpringJUnit4ClassRunner), It does a lot but to keep it simple it boot straps the spring container and injects all the dependencies like that you defined in your context file (Home-ctx.xml) including the one you inject into the test case. For more details look at these classes
http://static.springsource.org/spring/docs/2.5.x/api/org/springframework/test/context/TestContextManager.html
http://static.springsource.org/spring/docs/2.5.x/api/org/springframework/test/context/support/DependencyInjectionTestExecutionListener.html
To solve the problem with your main method, You have to load the spring context your self and inject the dependency some thing like this
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("/com/home/app/Home-ctx.xml");
new LoginDetailsTest().loginDetailsService = (LoginDetailsService) ctx.getBean(LoginDetailsService.class);
//now your main method should work
new LoginDetailsTest().testLoginDetails();

Related

Create JUnit for private method with ContextRefreshedEvent argument

I want to create a JUnit test for this private method:
#Component
public class ReportingProcessor {
#EventListener
private void collectEnvironmentData(ContextRefreshedEvent event) {
}
}
I tried this:
#SpringBootApplication
public class ReportingTest {
#Bean
ServletWebServerFactory servletWebServerFactory() {
return new TomcatServletWebServerFactory();
}
#Test
public void reportingTest() throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
GenericApplicationContext parent = new GenericApplicationContext();
parent.refresh();
ConfigurableApplicationContext context = new SpringApplicationBuilder(Configuration.class).parent(parent).run();
ContextRefreshedEvent refreshEvent = new ContextRefreshedEvent(context);
ReportingProcessor instance = new ReportingProcessor();
Method m = ReportingProcessor.class.getDeclaredMethod("collectEnvironmentData", ContextRefreshedEvent.class);
m.setAccessible(true);
m.invoke(instance, refreshEvent);
}
}
But I get exception: Caused by: org.springframework.context.ApplicationContextException: Unable to start ServletWebServerApplicationContext due to missing ServletWebServerFactory bean.
What is the proper way to implement a mocked object for ContextRefreshedEvent?
There is a whole lot of debate/insights on why you should not/avoid writing unit tests for private methods. Have a look at this SO question that should help you decide and provide more insights - How do I test a private function or a class that has private methods, fields or inner classes?
But, if you want to achieve what you have posted anyway; let me break down a few things which are failing the test.
Spring Boot tests need to be annotated with #SpringBootTest (if using JUnit 5) and additionally with #RunWith(SpringRunner.class) if you are using JUnit 4
You should not be creating an instance of your class with the new operator in the test, let the Test context automatically loads it using #Autowired or any other similar mechanism to inject the class.
To mock your input parameter and to invoke the private method you can use Powermockito library. Please note that if your scenario would not have required invoking a private method then mockito library should be enough for almost all mocking scenarios.
Below is the test that should work:
#SpringBootTest
public class ReportingProcessorTest {
#Autowired
ReportingProcessor reportingProcessor;
#Test
public void reportingTest() throws Exception {
ContextRefreshedEvent contextRefreshedEvent = PowerMockito.mock(ContextRefreshedEvent.class);
Whitebox.invokeMethod(reportingProcessor, "collectEnvironmentData", contextRefreshedEvent);
}
}

When do I have to create new instance while using #Autowired [duplicate]

This question already has answers here:
Why is my Spring #Autowired field null?
(21 answers)
Closed 4 years ago.
Until today I was 100 percent sure I don't have to create new instance of class when defining it as a bean.
Today I got a bit confused.
I will try to explain part of it in words as I think uploading all the code will make it hard to understand.
I created new REST project using intellij and Spring.
I created new class mapped it and added #RestController to it.
In this class I added property of another class that I created myself and added #Autowired to it.
I never created new instance of this class BUT I did add a bean configuration.
until now all worked fine.
I wanted to add ThreadPoolTaskScheduler logic so I opened new class, added new property ThreadPoolTaskScheduler and marked it with #Autowired.
I added a Bean for it:
#Bean
public ThreadPoolTaskScheduler threadPoolTaskScheduler(){
ThreadPoolTaskScheduler threadPoolTaskScheduler
= new ThreadPoolTaskScheduler();
threadPoolTaskScheduler.setPoolSize(5);
threadPoolTaskScheduler.setThreadNamePrefix(
"ThreadPoolTaskScheduler");
return threadPoolTaskScheduler;
}
Now in the main class if I don't send new instance of the class if will throw me null exception.
so this code is working:
public static void main(String[] args) {
SpringApplication.run(RestApiApplication.class, args);
TaskScheduler taskScheduler = new TaskScheduler(new ThreadPoolTaskScheduler());
taskScheduler.setTaskScheduler();
}
and this code is not:
public static void main(String[] args) {
SpringApplication.run(RestApiApplication.class, args);
TaskScheduler taskScheduler = new TaskScheduler();
taskScheduler.setTaskScheduler();
}
this is the TaskScheduler class:
#Controller
public class TaskScheduler {
#Autowired
ThreadPoolTaskScheduler threadPoolTaskScheduler;
TaskScheduler(){}
TaskScheduler(ThreadPoolTaskScheduler threadPoolTaskScheduler){
this.threadPoolTaskScheduler = threadPoolTaskScheduler;
}
public void setTaskScheduler(){
threadPoolTaskScheduler.schedule(
new ScheduledTask(),
new Date());
}
}
I can't figure out the reason get NULL for threadPoolTaskScheduler at setTaskScheduler, any idea?
If I definde TaskScheduler also as a bean it works ok, why do I have to? spring can handle everthing or nothing?
If you want me to add more code just tell me.
When you instantiate a bean itself, it does not make it managed anymore and thus all of the #Autowired dependencies will not be processed. In order to make sure that dependencies are injected properly, you will need to instantiate the bean through the Spring context. Spring needs to instantiate the whole beans trail in order to perform the dependency injection properly. Please check the IoC details in this link: https://docs.spring.io/spring/docs/3.2.x/spring-framework-reference/html/beans.html#beans-factory-class-ctor
Looking at your code, there are two ways to do this
Using Spring Boot
The class that will have the main method will look like this:
#SpringBootApplication
public class RestApiApplication implements CommandLineRunner {
#Autowired
private TaskScheduler ts;
public static void main(String[] args) {
SpringApplication.run(RestApiApplication.class, args);
}
#Override
public void run(String... args) throws Exception {
ts.<your-code>
}
}
The other way is to use the ApplicationContext to get the bean instance directly which works well if you need to perform some integration/unit testing.
You can get your bean object like:
public class RestApiApplication{
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(RestApiApplication.class, args);
TaskScheduler ts = context.getBean(TaskScheduler.class);
ts.setTaskScheduler()
}
}
But it's bad idea in real project use ApplicationContext like this example!
If you need REST you should use #RestController annotation.
Check this guide

How do you pass program arguments in a #SpringBootTest?

I'm building a spring boot application. I want to run it like this:
java -jar myjar.jar inputFile outputFile
How do I write a #SpringBootTest for this? I imagine that using #SpringBootTest would make Spring fail during startup because some of my code would say, "you need to provide an inputFile and outputFile". Is there a way to pass program arguments when using a #SpringBootTest?
I'm inferring from this answer that I may have to use a SpringApplicationBuilder to do this.
I thought I had the answer but I was wrong. This incorrect information may still be useful to some:
(This information is wrong. I think that some arguments can't be referred to in code as properties, but not all. I still don't know how to get the application arguments in a #SpringBootTest) I was confused because I didn't understand the terminology. The annotation has a parameter for "properties". I thought it was to point it at a property file, but the documentation says:
Properties in form key=value that should be added to the Spring Environment before the test runs.
The other piece of the terminology puzzle is that what I called "program arguments" the Spring docs refer to as "properties".
This is some additional relevant documentation: https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#boot-features-application-arguments
This is a workaround (not an answer). You can do something like this:
private SpringApplicationBuilder subject;
#Before
public void setUp() throws Exception {
subject = new SpringApplicationBuilder(Application.class);
}
#Test
public void requiresInputAndOutput() throws Exception {
thrown.expect(IllegalStateException.class);
subject.run();
}
#Test
public void happyPathHasNoErrors() throws Exception {
subject.run(EXISTING_INPUT_FILE, "does_not_exist");
}
I don't like this very much. It prevents me from using #Autowired elsewhere in my test.
if your program arguments is
--arg1=val1
before springboot version 2.2.0
you can use
#SpringBootTest({"arg1=val1"})
after springboot 2.2.0
you can use
#SpringBootTest(args={"--arg1=val1"})
I had the same issue. Out of the box, the SpringBootContextLoader (used by default with #SpringBootTest) always runs your app without any arguments. However, you can provide your own bootstrapper, which can then in turn provide your own SpringApplication that gets called to run your test. From there, you can override the run(String... args) method to provide whatever arguments you want.
For example, given a simple application:
#SpringBootApplication
public class Main {
public static void main(final String[] args) {
new SpringApplicationBuilder(Main.class).run(args);
}
#Bean
public ApplicationRunner app() {
return args -> System.out.println(args.getOptionNames());
}
}
You can inject test arguments with:
#ContextConfiguration(classes = Main.class)
#ExtendWith(SpringExtension.class)
#BootstrapWith(RestartAppsTest.Bootstrapper.class)
class RestartAppsTest {
static class Bootstrapper extends SpringBootTestContextBootstrapper {
static class ArgumentSupplyingContextLoader extends SpringBootContextLoader {
#Override
protected SpringApplication getSpringApplication() {
return new SpringApplication() {
#Override
public ConfigurableApplicationContext run(String... args) {
return super.run("--restart");
}
};
}
}
#Override
protected Class<? extends ContextLoader> getDefaultContextLoaderClass(Class<?> testClass) {
return ArgumentSupplyingContextLoader.class;
}
}
#Test
void testRestart() {
//
}
}
It's obviously a bit verbose, but it works. You could clean it up and make a nicer/reusable bootstrapper that looked for your own annotation (or possibly reuse JUnit Jupiter's #Arguments) that declared what arguments to supply (instead of hardcoding them).
You can use #SpringBootTest(classes=Application.class, args ={inputFile, outputFile}) if your app's main method looks a little different like
public static void main(String[] args){
SpringApplication.run(Application.class, args);
}
For me working example is
#SpringBootTest(args ={"--env", "test"})
class QuickFixServerAppTest {
#Test
void loadContextTest() {
}
}
is the same as passing the
--env test
argument when starting Spring
Normally you're writing tests for your services; not the boot strapper. Spring Boot will pass command line parameters to your classes - perhaps using the #Value annotation, which in turn will be parameters to your service. Consider testing your services using the SpringRunner. Here's an example from my code base.
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = {
Neo4jConfigTest.class,
FdTemplate.class,
FdServerIo.class,
MapBasedStorageProxy.class})
#ActiveProfiles({"dev", "fd-auth-test", "fd-client"})
public class TestEntityLinks {
#Autowired
private ContentModelService contentModelService;
#Autowired
private BatchService batchService;
#Test
public void doSomething () { ... }

Spring integration test with context hierarchy child context bean as application listener

I am new to Spring, forgive me if I am doing something silly. I am trying to write an integration test for my application which uses spring.
I am creating a context hierarchy, as follows
#Before
public void setup(){
parentContext = new AnnotationConfigApplicationContext(TestConfig.class);
// some more setup stuff here
}
In my test method, I am trying to create a new child context that has just one bean which is an application listener, that depends on beans in parent method.
public void test(){
childContext = new AnnotationConfigApplicationContext();
childContext.setParent(ctx);
register(TestConfig2.class);
childContext.refresh();
// some testing stuff here that generates events
}
The problem I am facing is that my bean from the child context is not getting notified on application events also my #Value annotations are not processed.
what am I doing wrong here?
Actually I figured out what was going wrong. My event publisher was in the parent context. I read on the spring forum, that spring context hierarchies work like class loaders. As in any beans loaded by the child Context are not visible to the parent context.
So I had to manually add the applicationlistener to the parent context.
parentContext.addApplicationListener(messageListener);
If want my childContext beans to get properties from parentContext I had to add the parentContext's PropertyPlaceholderConfigurer as a beanFactoryPostProcessor.
configurer = parentContext.getBean(PropertyPlaceholderConfigurer.class);
childContext.addBeanFactoryPostProcessor(configurer);
to sum it up, I had to do the following in my test method
public void test(){
childContext = new AnnotationConfigApplicationContext();
childContext.setParent(parentContext);
register(TestConfig2.class);
configurer = parentContext.getBean(PropertyPlaceholderConfigurer.class);
childContext.addBeanFactoryPostProcessor(configurer);
childContext.refresh();
MessageListener messageListener = childContext.getBean(MessageListener.class);
parentContext.addApplicationListener(messageListener);
// some testing stuff here that generates events
}
Declare
private static ClassPathXmlApplicationContext context;
at the method #Before
#BeforeClass
public static void setUpBeforeClass() throws Exception {
context = new ClassPathXmlApplicationContext("/WEB-INF/application-Context.xml");
}
at the methode #After
#AfterClass
public static void tearDownAfterClass() throws Exception {
context.close();
}
your method test
#Test
public void Test() {
//Your Code Here
}
I am also starting in spring

How do you inject jdbiFactory DAOs into a Dropwizard Command?

I'm starting to work with Dropwizard and I'm trying to create a Command that requires to use the database. If someone is wondering why I'd want to do that, I can provide good reasons, but this is not the point of my question anyway. It's about dependency inversion and Service initialization and run phases in Dropwizard.
Dropwizard encourages to use its DbiFactory to build DBI instances but in order to get one, you need an Environment instance and/or the database configuration:
public class ConsoleService extends Service<ConsoleConfiguration> {
public static void main(String... args) throws Exception {
new ConsoleService().run(args);
}
#Override
public void initialize(Bootstrap<ConsoleConfiguration> bootstrap) {
bootstrap.setName("console");
bootstrap.addCommand(new InsertSomeDataCommand(/** Some deps should be here **/));
}
#Override
public void run(ConsoleConfiguration config, Environment environment) throws ClassNotFoundException {
final DBIFactory factory = new DBIFactory();
final DBI jdbi = factory.build(environment, config.getDatabaseConfiguration(), "postgresql");
// This is the dependency I'd want to inject up there
final SomeDAO dao = jdbi.onDemand(SomeDAO.class);
}
}
As you can see, you have the configuration for your Service and its Environment in its run() method, but commands need to be added to the Service's bootstrap in its initialize() method.
So far, I've been able to achieve this by extending ConfiguredCommand in my Commands and creating DBI instances inside their run() methods, but this is a bad design, because dependencies should be injected into the object instead of creating them inside.
I'd prefer to inject DAOs or any other dependencies of my Commands through their constructor, but this seems currently impossible to me, as the Environment and the configuration are not accesible in Service initialization, when I need to create and add them to its bootstrap.
Does anyone know how to achieve this?
Can you use the EnvironmentCommand?
This is how I use Guice with Dropwizard. Inside your run() method add the line
Guice.createInjector(new ConsoleModule());
Create the class ConsoleModule
public class ConsoleModule extends AbstractModule {
public ConsoleModule(ConsoleConfiguration consoleConfig)
{
this.consoleConfig = consoleConfig;
}
protected void configure()
{
bind(SomeDAO.class).to(SomeDAOImpl.class).in(Singleton.class)
}
}

Categories