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);
Related
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 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.
I'm testing a web application using JUnit. The buisness layer of this application is writed in EJB stateless classes.
So I do "in container" tests with JUnit and Glassfish-embedded.
All works fine so far, EJBs are injected using lookup functions.
Here are a simple test case :
public class SupportTest {
private static EJBContainer container;
private static MyEJB myEjb;
#BeforeClass
public static void setUpServices() throws NamingException {
Map<String, Object> properties = new HashMap<String, Object>();
properties.put(EJBContainer.MODULES, new File("target/classes"));
container = EJBContainer.createEJBContainer(properties);
myEjb = (MyEJB) container.getContext().lookup("java:global/classes/MyEJB");
}
#Test
public void test() {
myEjb.doSomething("user_login");
}
}
Now I have a SessionScoped POJO (CDI) which keep information such as user login and so on.
This Pojo is injected inside a static class. Like this :
public class MyStaticClass {
public static boolean verifyLogin(String login) {
MySessionPojo mySessionPojo = CDI.current().select(MySessionPojo.class).get();
return mySessionPojo.getLogin().equals(login);
}
}
This static class is used in EJB to secure the buisness code, like this :
#Stateless
public class MyEJB {
public void doSomething(String login) {
if(MyStaticClass.verifyLogin(login)){
//do something
}
}
}
Inside a normal Glassfish 4.1 server, the injection of the POJO inside the static class works fine.
Inside the Glassfish-embedded, the POJO injection fails with this message :
WELD-001303: No active contexts for scope type javax.enterprise.context.SessionScoped
I assume this is because there is no Http Session bound to it.
Is there a way to simulate/create à SessionContext programmatically?
Thanks.
Ok, I finally find a workaround. I use the framework JMockit to replace the static class by a mock class, with fake methods which always return TRUE. (I had already tested Mockito and PowerMock, but both didn't work).
I am using this Spring AOP code in my Spring Boot starter project in STS. After debugging this for some time I don't see any problem with the AspectJ syntax. The Maven dependencies are generated by STS for a AOP starter project. Is there a glaring omission in this code like an annotation ? The other problem could be with the AOP starter project or with the way I try to test the code in a #PostConstruct method.
I installed AJDT but it appears STS should show AspectJ markers in the IDE on its own. Right ? I don't see the markers. What other AspectJ debugging options are included in STS ? -Xlint is what I used in Eclipse/AJDT.
StateHandler.java
public class StateHandler<EVENTTYPE extends EventType> {
private State<EVENTTYPE> state;
private Event<EVENTTYPE> event;
public StateHandler(State<EVENTTYPE> state, Event<EVENTTYPE> event) {
this.state = state;
this.event = event;
}
public void handle( Event<EVENTTYPE> event ){
state = state.handle( event );
}
public State<EVENTTYPE> getState() {
return state;
}
}
DeviceLogger .java
#Aspect
#Component
public class DeviceLogger {
private static Logger logger = Logger.getLogger("Device");
#Around("execution(* com.devicemachine.StateHandler.*(..))")
public void log() {
logger.info( "Logger" );
}
}
LoggerApplication.java
#SpringBootApplication
public class LoggerApplication {
private static Logger logger = Logger.getLogger("Device");
public static void main(String[] args) {
SpringApplication.run(LoggerApplication.class, args);
}
#PostConstruct
public void log(){
DeviceState s = DeviceState.BLOCKED;
StateHandler<DeviceEvent> sh = new StateHandler<DeviceEvent>( s,
Event.block(DeviceEvent.BLOCKED, "AuditMessage") );
sh.handle(Event.block(DeviceEvent.UNBLOCKED, "AuditMessage"));
}
}
There are 3 obvious things wrong and 1 not so obvious wrong.
Your aspect is wrong and breaks proper method execution. When using an around aspect you must always return Object and use a ProceedingJoinPoint and call proceed() on that.
You are creating new instances of classes yourself, Spring, by default, uses proxy based AOP and will only proxy beans it knows.
In a #PostConstruct method it might be that proxies aren't created yet and that nothing is being intercepted
You need to use class based proxies for that to be enabled add spring.aop.proxy-target-class=true to your application.properties. By default JDK Dynamic Proxies are used which are interface based.
Fix Aspect
Your current aspect doesn't use a ProceedingJoinPoint and as such never does the actual method call. Next to that if you now would have a method that returns a value it would all of a sudden return null. As you aren't calling proceed on the ProceedingJoinPoint.
#Around("execution(* com.devicemachine.StateHandler.*(..))")
public Object log(ProceedingJoinPoint pjp) throws Throwable {
logger.info( "Logger" );
return pjp.proceed();
}
Create a bean to fix proxying and #PostConstruct
#SpringBootApplication
public class LoggerApplication {
private static Logger logger = Logger.getLogger("Device");
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(LoggerApplication.class, args);
StateHandler<DeviceEvent> sh = context.getBean(StateHandler<DeviceEvent>.class);
sh.handle(Event.block(DeviceEvent.UNBLOCKED, "AuditMessage"));
}
#Bean
public StateHandler<DeviceEvent> auditMessageStateHandler() {
return new StateHandler<DeviceEvent>(DeviceState.BLOCKED, Event.block(DeviceEvent.BLOCKED, "AuditMessage") );
}
}
Add property to enable class proxies
In your application.properties in src\main\resources add the following property with a value of true
spring.aop.proxy-target-class=true
I'm being hit with the issue that spock doesn't allow Mocks to be created outside of the specification - How to create Spock mocks outside of a specification class?
This seems to be still outstanding so am asking is that giving that i've got a complex and nested DI graph what is the most efficient way to 'inject' a mock representation deep in the graph?
Ideally, I have one bean definition set for normal deployment and another when running unit tests and it is this definition set being the applicable Mocks
e.g.
#Configuration
#Profile("deployment")
public class MyBeansForDeployment {
#Bean
public MyInterface myBean() {
return new MyConcreateImplmentation();
}
}
&&
#Configuration
#Profile("test")
public class MyBeansForUnitTests {
#Bean
public MyInterface myBean() {
return new MyMockImplementation();
}
}
Since Spock 1.1, you can create mocks outside of a specification class (detached mocks). One of the options is DetachedMockFactory. Take a look at the documentation or my answer to the question you linked.
You could try to implement a BeanPostProcessor that will replace the beans that you want with test doubles, such as shown below:
public class TestDoubleInjector implements BeanPostProcessor {
...
private static Map<String, Object[]> testDoubleBeanReplacements = new HashMap<>();
public void replaceBeanWithTestDouble(String beanName, Object testDouble, Class testDoubleType) {
testDoubleBeanReplacements.put(beanName, new Object[]{testDouble, testDoubleType});
}
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (testDoubleBeanReplacements.containsKey(beanName)) {
return testDoubleBeanReplacements.get(beanName)[TEST_DOUBLE_OBJ];
}
return bean;
}
In your test, setup your mocks like shown below before initializing the application context. Make sure to include the TestDoubleInjector as a bean in your test context.
TestDoubleInjector testDoubleInjector = new TestDoubleInjector()
testDoubleInjector.replaceBeanWithTestDouble('beanToReplace', mock(MyBean.class), MyBean.class)
It could be done using HotSwappableTargetSource
#WebAppConfiguration
#SpringApplicationConfiguration(TestApp)
#IntegrationTest('server.port:0')
class HelloSpec extends Specification {
#Autowired
#Qualifier('swappableHelloService')
HotSwappableTargetSource swappableHelloService
def "test mocked"() {
given: 'hello service is mocked'
def mockedHelloService = Mock(HelloService)
and:
swappableHelloService.swap(mockedHelloService)
when:
//hit endpoint
then:
//asserts
and: 'check interactions'
interaction {
1 * mockedHelloService.hello(postfix) >> { ""Mocked, $postfix"" as String }
}
where:
postfix | _
randomAlphabetic(10) | _
}
}
And this is TestApp (override the bean you want to mock with proxy)
class TestApp extends App {
//override hello service bean
#Bean(name = HelloService.HELLO_SERVICE_BEAN_NAME)
public ProxyFactoryBean helloService(#Qualifier("swappableHelloService") HotSwappableTargetSource targetSource) {
def proxyFactoryBean = new ProxyFactoryBean()
proxyFactoryBean.setTargetSource(targetSource)
proxyFactoryBean
}
#Bean
public HotSwappableTargetSource swappableHelloService() {
new HotSwappableTargetSource(new HelloService());
}
}
Have a look at this example https://github.com/sf-git/spock-spring