I have a Spring JUnit tester class MySimpleTester:
#
RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations={"classpath:/spring/mySimpleConfig.xml"})
public class MySimpleTester {
#Before
public void setUp() throws Exception {
myAdapter = (MyAdapter) applicationContext.getBean("myAdapter");
}
#test
public void testGetSimpleList() {
List<SimpleLink> simpleList = **myAdapter.getSimpleLinksList**();
}
...
...
In the adapter class I have:
public MyAdapter {
public List<SimpleLink> getSimpleLinksList() {
List<SimpleLink> simLinks = null;
String environment = AppFactory.getPropertiesObj();
...
...
class AppFactory implements ApplicationContextAware {
private static ApplicationContext context;
public void setApplicationContext(ApplicationContext acontext) {
context = acontext;
}
public getPropertiesObj() {
return getAppContext().getBean("propertiesBean");
}
I get NullPointerException and see that ApplicationContext is Null here.
However at the SpringJUnitTestRunner class MySimpleTester I could find the applicationContext to be initialized correctly. I am not including the mySimpleConfig.xml and included files. The method in MyAdapter class getSimpleLinksList() works perfectly fine from the web application when run in the application server, and the appcontext is obtained there.
Only from the Spring tester is it not able to reach the static application context AppFactory class, as it is called statically through AppFactory.getPropertiesObj(). I had the classpath set correctly as other test classes are executing.
If you want to access the current ApplicationContext in MySimpleTester:-
public class MySimpleTester {
#Autowired
ApplicationContext applicationContext;
#Before
public void setUp() throws Exception {
myAdapter = (MyAdapter) applicationContext.getBean("myAdapter");
}
#test
public void testGetSimpleList() {
List<SimpleLink> simpleList = **myAdapter.getSimpleLinksList**();
}
I think it is happening as multiple application contexts are created. The AplliCationContext object is supposed to be singleton. But when from the static method we call the applicationContext again it is refering to altogether different confirguration. The ApplicationContext is not even initialised there.
This does not happen when the same module is called from Spring MVC webcontanier. It happens only when you try to use Spring tester classes RunWith(SpringJUnit4ClassRunner.class). I can pass the AppContext in the business method but I do not want to change the bsiness method signature. I found some threads in spring community with similar issue.
Related
I am currently writing a Spring boot application that will perform loadtests on another app. I want to use Gatling to manage the tests, but I need it to access the configuration that I defined in beans of my Spring app.
Here is what I would like to see working :
public class MySimulation extends Simulation {
#Autowired
private JMSConnectionFactoryBeanClass myConnectionFactory;
public MySimulation() {
JmsProtocolBuilder jmsProtocol = jms.connectionFactory(myBean);
ScenarioBuilder scn = scenario("My Simulation Scenario")
.exec(
jms("test")
.send()
.queue("myQueue")
.textMessage("message")
);
{
setUp(
scn.injectOpen(rampUsers(10).during(5))
).protocols(jmsProtocol);
}
}
When I hardcode the configuration into the simulation class and remove all #Autowired thing, everything works, so it must be comming from the dependency injection. Does anybody know if there is a way to us spring beans in a gatling simulation ?
Following Stéphane Landelle advice, here is what I came up with, but instead of creating my app context inside of the simulation, I figured out how to run the simulation along with my spring app using gatling API :
public class GatlingRunner {
public static void run() {
GatlingPropertiesBuilder props = new GatlingPropertiesBuilder();
props.simulationClass("path.to.Simulation");
Gatling.fromMap(props.build());
}
}
This is how I modified my spring app :
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
GatlingRunner.run();
}
}
Finally, to use spring beans in the simulation, I wrote a context provider that would make the link between spring and gatling :
#Component
public class ApplicationContextProvider implements ApplicationContextAware {
private static ApplicationContext context;
public static ApplicationContext getApplicationContext() {
return context;
}
#Override
public void setApplicationContext(ApplicationContext context)
throws BeansException {
ApplicationContextProvider.context = context;
}
}
Now, to get a bean inside of the simulation, all I needed was this :
Bean myBean = ApplicationContextProvider.getApplicationContext()
.getBean("myBean", Bean.class)
You can't use #Autowired. You have to create an ApplicationContext programmatically and pull the JMSConnectionFactoryBeanClass from it.
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = MySimulation.class, loader = SpringApplicationContextLoader.class)
public class MySimulation extends GatlingTest {
#Autowired
private JMSConnectionFactoryBeanClass myConnectionFactory;
#Test
public void test() {
...
}
}
My Spring Boot project uses JUnit 5. I'd like to setup an integration test which requires a local SMTP server to be started, so I implemented a custom extension:
public class SmtpServerExtension implements BeforeAllCallback, AfterAllCallback {
private GreenMail smtpServer;
private final int port;
public SmtpServerExtension(int port) {
this.port = port;
}
#Override
public void beforeAll(ExtensionContext extensionContext) {
smtpServer = new GreenMail(new ServerSetup(port, null, "smtp")).withConfiguration(GreenMailConfiguration.aConfig().withDisabledAuthentication());
smtpServer.start();
}
#Override
public void afterAll(ExtensionContext extensionContext) {
smtpServer.stop();
}
}
Because I need to configure the server's port I register the extension in the test class like this:
#SpringBootTest
#AutoConfigureMockMvc
#ExtendWith(SpringExtension.class)
#ActiveProfiles("test")
public class EmailControllerIT {
#Autowired
private MockMvc mockMvc;
#Autowired
private ObjectMapper objectMapper;
#Value("${spring.mail.port}")
private int smtpPort;
#RegisterExtension
// How can I use the smtpPort annotated with #Value?
static SmtpServerExtension smtpServerExtension = new SmtpServerExtension(2525);
private static final String RESOURCE_PATH = "/mail";
#Test
public void whenValidInput_thenReturns200() throws Exception {
mockMvc.perform(post(RESOURCE_PATH)
.contentType(APPLICATION_JSON)
.content("some content")
).andExpect(status().isOk());
}
}
While this is basically working: How can I use the smtpPort annotated with #Value (which is read from the test profile)?
Update 1
Following your proposal I created a custom TestExecutionListener.
public class CustomTestExecutionListener implements TestExecutionListener {
#Value("${spring.mail.port}")
private int smtpPort;
private GreenMail smtpServer;
#Override
public void beforeTestClass(TestContext testContext) {
smtpServer = new GreenMail(new ServerSetup(smtpPort, null, "smtp")).withConfiguration(GreenMailConfiguration.aConfig().withDisabledAuthentication());
smtpServer.start();
};
#Override
public void afterTestClass(TestContext testContext) {
smtpServer.stop();
}
}
The listener is registered like this:
#TestExecutionListeners(value = CustomTestExecutionListener.class, mergeMode = MERGE_WITH_DEFAULTS)
When running the test the listener gets called but smtpPort is always 0, so it seems as if the #Value annotation is not picked up.
I don't think you should work with Extensions here, or in general, any "raw-level" JUnit stuff (like lifecycle methods), because you won't be able to access the application context from them, won't be able to execute any custom logic on beans and so forth.
Instead, take a look at Spring's test execution listeners abstraction
With this approach, GreenMail will become a bean managed by spring (probably in a special configuration that will be loaded only in tests) but since it becomes a bean it will be able to load the property values and use #Value annotation.
In the test execution listener you'll start the server before the test and stop after the test (or the whole test class if you need that - it has "hooks" for that).
One side note, make sure you mergeMode = MergeMode.MERGE_WITH_DEFAULTS as a parameter to #TestExecutionListeners annotation, otherwise some default behaviour (like autowiring in tests, dirty context if you have it, etc) won't work.
Update 1
Following Update 1 in the question. This won't work because the listener itself is not a spring bean, hence you can't autowire or use #Value annotation in the listener itself.
You can try to follow this SO thread that might be helpful, however originally I meant something different:
Make a GreenMail a bean by itself:
#Configuration
// since you're using #SpringBootTest annotation - it will load properties from src/test/reources/application.properties so you can put spring.mail.port=1234 there
public class MyTestMailConfig {
#Bean
public GreenMail greenMail(#Value(${"spring.mail.port"} int port) {
return new GreenMail(port, ...);
}
}
Now this configuration can be placed in src/test/java/<sub-package-of-main-app>/ so that in production it won't be loaded at all
Now the test execution listener could be used only for running starting / stopping the GreenMail server (as I understood you want to start it before the test and stop after the test, otherwise you don't need these listeners at all :) )
public class CustomTestExecutionListener implements TestExecutionListener {
#Override
public void beforeTestClass(TestContext testContext) {
GreenMail mailServer =
testContext.getApplicationContext().getBean(GreenMail.class);
mailServer.start();
}
#Override
public void afterTestClass(TestContext testContext) {
GreenMail mailServer =
testContext.getApplicationContext().getBean(GreenMail.class);
mailServer.stop();
}
}
Another option is autowiring the GreenMail bean and using #BeforeEach and #AfterEach methods of JUnit, but in this case you'll have to duplicate this logic in different Test classes that require this behavour. Listeners allow reusing the code.
I am using Alfresco Process Services and have created a created a spring boot project for custom logic like TaskListeners and Delegations. I am creating the jar file from this maven project and copying it into webapps/activiti-app/WEB-INF/lib folder.
I have a simple TaskListener as below which is getting called on Task start. But the #Autowired variables are always null.
package com.activiti.extension.bean;
#Component("myTaskListener")
public class MyTaskListener implements TaskListener {
#Autowired
UserService userService;
#Override
public void notify(DelegateTask task) {
logger.info("userService: " +userService); // Always prints null
}
Finally I was able to make it work. I was putting the task listener in the class field of the Task properties with full package name. Now I am putting Delegate expression like ${myTaskListener} and it worked...
Thank you all for your time and help
This is because your your MyTaskListener is annotated as #Component or at least being ignored by spring during init. for auto-wiring capabilities spring requires this annotation (or similar to this) under the provided #ComponentScan packages to consider the class as a bean otherwise it will take as a normal java class and hence the #autowired is of no use in your case.
This below code is worked for me
#Component
public class MyTaskListener implements TaskListener {
public static UserService getUserServiceObject() {
return SpringApplicationContextHolder.getApplicationContext().getBean(UserService.class);
}
#Override
public void notify(DelegateTask delegateTask) {
//UserService Object, It is not null now
getUserServiceObject();
}
}
#Component
public class SpringApplicationContextHolder implements ApplicationContextAware {
private static ApplicationContext applicationContext = null;
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
There is also one more way to get to your custom service "UserService" using Alfresco Spring Application context.
First access ServiceRegistry (registry used for accessing Alfresco Services but also any other custom service):
ServiceRegistry serviceRegistry = (ServiceRegistry) Context.getProcessEngineConfiguration().getBeans().get(ActivitiConstants.SERVICE_REGISTRY_BEAN_KEY);
Then get custom service UserService:
QName qname = QName.createQName("UserService");
UserService userService = (UserService) serviceRegistry.getService(qname);
I have custom TestExecutionListener:
public class CustomExecutionListener extends AbstractTestExecutionListener {
#Override
public void beforeTestMethod(TestContext testContext) throws Exception {
// some code ...
}
#Override
public void afterTestMethod(TestContext testContext) throws Exception {
// some code ...
}
}
In my test class I configure it as follows:
#TestExecutionListeners({
DirtiesContextTestExecutionListener.class,
DependencyInjectionTestExecutionListener.class,
CustomExecutionListener.class
})
class MyTestClass {
private static ApplicationContext appContext;
#BeforeAll
static void init() {
appContext = new AnnotationConfigWebApplicationContext();
// register some configs for context here
}
#Test
void test() {
}
}
And CustomExecutionListener doesn't work - in debugger I even don't go there. I suppose that may be problem in the way I create ApplicationContext: may be TestContext encapsulates not my appContext? (I don't properly understand how TestContext is creating. May be someone could explain?) But even then it should at least go to the beforeTestMethod in lestener? Or not?
And second question: if it really encapsulates not my appContext how I can fix that? I. e. set my appContext to testContext.getApplicationContext()? I need to be able to extract beans from my appContext like testContext.getApplicationContext().getBean(...).
For starters, the TestExecutionListener is only supported if you are using the Spring TestContext Framework (TCF).
Since you are using JUnit Jupiter (a.k.a., JUnit 5), you need to annotate your test class with #ExtendWith(SpringExtension.class) or alternatively with #SpringJUnitConfig or #SpringJUnitWebConfig.
Also, you should not create your ApplicationContext programmatically. Rather, you let the TCF do that for you -- for example, by specifying declaratively which configuration classes to use via #ContextConfiguration, #SpringJUnitConfig, or #SpringJUnitWebConfig.
In general, I recommend you read the Testing chapter of the Spring Reference Manual, and if that doesn't help enough you can certainly find tutorials for "integration testing with Spring" online.
Regards,
Sam (author of the Spring TestContext Framework)
Did you try #Before which is not required static methods??
private static ApplicationContext appContext;
#Before
public void init() {
if(appContext == null) {
appContext = new AnnotationConfigWebApplicationContext();
// register some configs for context here
}
}
I try to benchmark some of the methods of my Spring (with maven) project. I need to use #Autowired and #Inject on several fields in my project. While I run my project, it works well. But JMH always gets NullPointerException with #Autowired/#Inject fields.
public class Resources {
private List<Migratable> resources;
#Autowired
public void setResources(List<Migratable> migratables) {
this.resources = migratables;
}
public Collection<Migratable> getResources() {
return resources;
}
}
My Benchmark class
#State(Scope.Thread)
public class MyBenchmark {
#State(Scope.Thread)
public static class BenchmarkState {
Resources res;
#Setup
public void prepare() {
res = new Resources();
}
}
#Benchmark
public void testBenchmark(BenchmarkState state, Blackhole blackhole) {
blackhole.consume(state.res.getResources());
}
}
When I run my benchmark, it get NullPointerException at Resources.getResources()
More specifically at resources.
It cannot Autowire setResources(). But if I run my project(exclude benchmark), it works fine.
How can I get rid of this NullPointerException with Autowired field while benchmarking?
Here is an example of how to run Spring-based benchmarks: https://github.com/stsypanov/spring-boot-benchmark.
Basically what you need is to store a reference to your application context as a field of benchmarks class, initialize the context in #Setup method and close it in #TearDown. Something like this:
#State(Scope.Thread)
#OutputTimeUnit(TimeUnit.MICROSECONDS)
#BenchmarkMode(value = Mode.AverageTime)
public class ProjectionVsDtoBenchmark {
private ManyFieldsRepository repository;
private ConfigurableApplicationContext context;
#Setup
public void init() {
context = SpringApplication.run(Application.class);
context.registerShutdownHook();
repository = context.getBean(ManyFieldsRepository.class);
}
#TearDown
public void closeContext(){
context.close();
}
}
The logic that you are going to measure must be encapsulated in a method of Spring component called from #Benchmark annotated method. Remember general rules of benchmarking to make sure your measurements are correct, e.g. use Blackhole or return value from the method to prevent compiler from DCE.
Try to use
#RunWith(SpringJUnit4ClassRunner.class) and #ContextConfiguration(locations = {...}) on the test class. This should initialize Spring TestContext Framework and let you autowire dependencies.
If this doesn't work, then you have to start Spring ApplicationContext explicitly as a part of you #Setup annotated method, using either of
ClassPathXmlApplicationContext, FileSystemXmlApplicationContext or
WebXmlApplicationContext and resolve beans from that context:
ApplicationContext context = new ChosenApplicationContext("path_to_your_context_location");
res = context.getBean(Resources.class);