I'm using PowerMock and Mockito to test a Spring controller.
I have the class TestController defined (see below, Snipper #1), and a Unit Test defined for it (see below, Snipper #2). But when I try to unit test it I get an exception (see below, Snipper #3).
If I remove #InjectMocks, remove the TestController instantiation on definition, and do controllerUT = new TestController() in the test function, it works fine (see below, Snipper #4).
This leads me to believe that the static replacement does not happen before #InjectMocks, and my question is if this is the way things work, or am I doing something wrong? Is there a better way to design the code to avoid this issue? I'm guessing people use static log assignment (I did not invent this) so someone must have run into this issue before...
Thanks!
Snippet #1
#Controller
#RequestMapping("/api/test")
public class TestController {
private static final Logger LOG = LoggerFactory.getLogger(TestController.class);
#Autowired
private GeneralService generalService;
#RequestMapping(method=RequestMethod.GET)
public void doSomethingUseful(
HttpServletRequest request,
HttpServletResponse response) {
// nothing userful to do right now
}
}
Snippet #2
#RunWith(PowerMockRunner.class)
#PrepareForTest({TestController.class, LoggerFactory.class})
public class TestControllerTest {
#InjectMocks
private TestController controllerUT = new TestController();
#Mock
private GeneralService service;
#Mock
private Logger loggerMock;
#Mock
private HttpServletRequest request;
#Mock
private HttpServletResponse response;
#Before
public void setUp() {
PowerMockito.mockStatic(LoggerFactory.class);
when(LoggerFactory.getLogger(any(Class.class))).
thenReturn(loggerMock);
}
#Test
public void doSomethingUsefulTest() {
controllerUT.doSomethingUseful(request, response);
assert(true);
}
}
Snippet #3
Failed to auto configure default logger context
Reported exception:
ch.qos.logback.core.joran.spi.JoranException: Parser configuration error occurred
at ch.qos.logback.core.joran.event.SaxEventRecorder.buildSaxParser(SaxEventRecorder.java:86)
at ch.qos.logback.core.joran.event.SaxEventRecorder.recordEvents(SaxEventRecorder.java:57)
at ch.qos.logback.core.joran.GenericConfigurator.doConfigure(GenericConfigurator.java:132)
at ch.qos.logback.core.joran.GenericConfigurator.doConfigure(GenericConfigurator.java:96)
at ch.qos.logback.core.joran.GenericConfigurator.doConfigure(GenericConfigurator.java:55)
at ch.qos.logback.classic.util.ContextInitializer.configureByResource(ContextInitializer.java:75)
at ch.qos.logback.classic.util.ContextInitializer.autoConfig(ContextInitializer.java:148)
at org.slf4j.impl.StaticLoggerBinder.init(StaticLoggerBinder.java:84)
at org.slf4j.impl.StaticLoggerBinder.<clinit>(StaticLoggerBinder.java:54)
at org.slf4j.LoggerFactory.bind(LoggerFactory.java:128)
at org.slf4j.LoggerFactory.performInitialization(LoggerFactory.java:108)
at org.slf4j.LoggerFactory.getILoggerFactory(LoggerFactory.java:279)
at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:252)
at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:265)
at com.basicservice.controller.TestController.<clinit>(TestController.java:39)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:169)
at javassist.runtime.Desc.getClassObject(Desc.java:43)
at javassist.runtime.Desc.getClassType(Desc.java:152)
at javassist.runtime.Desc.getType(Desc.java:122)
at javassist.runtime.Desc.getType(Desc.java:78)
at com.basicservice.controller.TestControllerTest.<init>(TestControllerTest.java:44)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.createTestInstance(PowerMockJUnit44RunnerDelegateImpl.java:188)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.createTest(PowerMockJUnit44RunnerDelegateImpl.java:173)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.invokeTestMethod(PowerMockJUnit44RunnerDelegateImpl.java:195)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.runMethods(PowerMockJUnit44RunnerDelegateImpl.java:148)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$1.run(PowerMockJUnit44RunnerDelegateImpl.java:122)
at org.junit.internal.runners.ClassRoadie.runUnprotected(ClassRoadie.java:34)
at org.junit.internal.runners.ClassRoadie.runProtected(ClassRoadie.java:44)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.run(PowerMockJUnit44RunnerDelegateImpl.java:120)
at org.powermock.modules.junit4.common.internal.impl.JUnit4TestSuiteChunkerImpl.run(JUnit4TestSuiteChunkerImpl.java:102)
at org.powermock.modules.junit4.common.internal.impl.AbstractCommonPowerMockRunner.run(AbstractCommonPowerMockRunner.java:53)
at org.powermock.modules.junit4.PowerMockRunner.run(PowerMockRunner.java:42)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
Caused by: java.lang.ClassCastException: org.apache.xerces.jaxp.SAXParserFactoryImpl cannot be cast to javax.xml.parsers.SAXParserFactory
at javax.xml.parsers.SAXParserFactory.newInstance(SAXParserFactory.java:128)
at ch.qos.logback.core.joran.event.SaxEventRecorder.buildSaxParser(SaxEventRecorder.java:79)
... 42 more
Snippet #4
#RunWith(PowerMockRunner.class)
#PrepareForTest({TestController.class, LoggerFactory.class})
public class TestControllerTest {
private TestController controllerUT;
#Mock
private GeneralService service;
#Mock
private Logger loggerMock;
#Mock
private HttpServletRequest request;
#Mock
private HttpServletResponse response;
#Before
public void setUp() {
PowerMockito.mockStatic(LoggerFactory.class);
when(LoggerFactory.getLogger(any(Class.class))).
thenReturn(loggerMock);
}
#Test
public void doSomethingUsefulTest() {
controllerUT = new TestController();
controllerUT.doSomethingUseful(request, response);
assert(true);
}
}
One thing to remeber is that #InjectMocks respect static and final fields i.e. it does not inject mocks in static or final fields. Also note that PowerMock has to spawn a new ClassLoader in order to "instrument" classes, which probably explains the snippet #3.
While I didn't explored your project's ins and outs, I believe you might want to prepare all the Logback/slf4j classes if you might want to use PowerMock. Still keep in mind that Powermock and Mockito are different project and might not work together as one could think.
Related
Note: Question is about standalone (using Main class) behaviour of the unreleased Camel 3.0.0-M2 version which has many enhancements for standalone mode over Camel 2.x - the code below is not intended to work on Camel 2.x
Problem: I noticed that modifications on the properties component as described in [1] do not affect config properties injected into beans. In my case I want to set the pc's environmentVariableMode to override (fallback is the default).
While the override was effective on the route, the bean got injected with the original property value.
The property "hi" in application.properties is set to "Hello" and the environment variable "hi" is set to "Huhu" which should override the former when environmentVariableMode is set to override (2).
When run:
System env var hi=Huhu
14:34:02.282 [Camel (camel-1) thread #2 - timer://foo] INFO route1 - Huhu from route
14:34:02.297 [Camel (camel-1) thread #2 - timer://foo] INFO route1 - Hello from bean
Code:
public class MyApplication {
private MyApplication() {
}
public static void main(String[] args) throws Exception {
Main main = new Main();
main.addConfigurationClass(MyConfiguration.class);
main.addRouteBuilder(MyRouteBuilder.class);
main.run(args);
}
}
public class MyConfiguration {
#BindToRegistry
public MyBean myBean(#PropertyInject("hi") String hi) {
return new MyBean(hi);
}
}
public class MyBean {
private String hi;
public MyBean(String hi) {
this.hi = hi;
}
public String hello() {
return hi + " from bean";
}
}
public class MyRouteBuilder extends RouteBuilder {
#Override
public void configure() throws Exception {
CamelContext context = getContext();
PropertiesComponent pc = context.getComponent("properties", PropertiesComponent.class);
pc.setEnvironmentVariableMode(PropertiesComponent.ENVIRONMENT_VARIABLES_MODE_OVERRIDE); //default is FALLBACK
System.out.println("System env var hi=" + System.getenv("hi"));
from("timer:foo?repeatCount=1")
.log("${properties:hi} from route")
.bean("myBean")
.log("${body}");
}
}
application.properties:
hi = Hello
The only way I could get it to work was to override Main#postProcessCamelContext -- is this really the way it is intended to use? Or is there a more idiomatic way?
public class MyApplication extends Main {
private MyApplication(String[] args) throws Exception {
addConfigurationClass(MyConfiguration.class);
addRouteBuilder(MyRouteBuilder.class);
run(args);
}
#Override
protected void postProcessCamelContext(CamelContext camelContext) throws Exception {
PropertiesComponent pc = camelContext.getComponent("properties", PropertiesComponent.class);
pc.setEnvironmentVariableMode(PropertiesComponent.ENVIRONMENT_VARIABLES_MODE_OVERRIDE);
super.postProcessCamelContext(camelContext);
}
public static void main(String[] args) throws Exception {
new MyApplication(args);
}
}
A suggestion to Camel development: Wouldn't it make more sense to set environmentVariableMode to override by default instead of fallback, especially when thinking about container deployments: Environment variables take precedence over system properties which take precedence over application configuration (e.g. application.properties)?
[1] https://github.com/apache/camel/blob/master/components/camel-properties/src/main/docs/properties-component.adoc
Yeah its better to have ENV override, you are welcome to log a JIRA and work on a github PR. We love contributions
http://camel.apache.org/support.html
I logged a ticket: https://issues.apache.org/jira/browse/CAMEL-13502
Okay this has now been implement to be the default mode, and you can also configure this from the application.properties file etc: https://github.com/apache/camel/blob/master/examples/camel-example-main/src/main/resources/application.properties#L23
And the issue with #PropertyInject has also been fixed, in Camel v3.0M3
I want to test below camel route. All the example which i find online has route starting with file, where as in my case i have a spring bean method which is getting called every few minutes and finally message is transformed and moved to jms as well as audit directory.
I am clue less on write test for this route.
All i have currently in my test case is
Mockito.when(tradeService.searchTransaction()).thenReturn(dataWithSingleTransaction);
from("quartz2://tsTimer?cron=0/20+*+8-18+?+*+MON,TUE,WED,THU,FRI+*")
.bean(TradeService.class)
.marshal()
.jacksonxml(true)
.to("jms:queue:out-test")
.to("file:data/test/audit")
.end();
Testing with Apache Camel and Spring-Boot is really easy.
Just do the following (the example below is an abstract example just to give you a hint how you can do it):
Write a Testclass
Use the Spring-Boot Annotations to configure the test class.
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
#RunWith(SpringRunner.class)
public class MyRouteTest {
#EndpointInject(uri = "{{sourceEndpoint}}")
private ProducerTemplate sourceEndpoint;
....
public void test() {
// send your body to the endpoint. See other provided methods too.
sourceEndpoint.sendBody([your input]);
}
}
In the src/test/application.properties:
Configure your Camel-Endpoints like the source and the target:
sourceEndpoint=direct:myTestSource
Hints:
It's good not to hardwire your start-Endpoint in the route directly when using spring-boot but to use the application.properties. That way it is easier to mock your endpoints for unit tests because you can change to the direct-Component without changing your source code.
This means instead of:
from("quartz2://tsTimer?cron=0/20+*+8-18+?+*+MON,TUE,WED,THU,FRI+*")
you should write:
from("{{sourceEndpoint}}")
and configure the sourceEndpoint in your application.properties:
sourceEndpoint=quartz2://tsTimer?cron=0/20+*+8-18+?+*+MON,TUE,WED,THU,FRI+*
That way you are also able to use your route for different situations.
Documentation
A good documentation about how to test with spring-boot can be found here: https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html
For Apache Camel: http://camel.apache.org/testing.html
#the hand of NOD Thanks for your hints, i was going into completely wrong direction. After reading your answer i was able to write the basic test and from this i think i can take it forward.
Appreciate your time, however i see that based on my route it should drop an XML file to audit directory which is not happening.
Look like intermediate steps are also getting mocked, without I specifying anything.
InterceptSendToMockEndpointStrategy - Adviced endpoint [xslt://trans.xslt] with mock endpoint [mock:xslt:trans.xslt]
INFO o.a.c.i.InterceptSendToMockEndpointStrategy - Adviced endpoint [file://test/data/audit/?fileName=%24%7Bheader.outFileName%7D] with mock endpoint [mock:file:test/data/audit/]
INFO o.a.camel.spring.SpringCamelContext - StreamCaching is not in use. If using streams then its recommended to enable stream caching. See more details at http://camel.apache.org/stream-caching.html
TradePublisherRoute.java
#Override
public void configure() throws Exception {
logger.info("TradePublisherRoute.configure() : trade-publisher started configuring camel route.");
from("{{trade-publisher.sourceEndpoint}}")
.doTry()
.bean(tradeService)
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
String dateStr = Constant.dateFormatForFileName.format(new Date());
logger.info("this is getting executed : " + dateStr);
exchange.setProperty(Constant.KEY_INCOMING_XML_FILE_NAME, "REQ-" + dateStr + Constant.AUDIT_FILE_EXTENSION);
exchange.setProperty(Constant.KEY_OUTGOING_XML_FILE_NAME, "RESP-" + dateStr + Constant.AUDIT_FILE_EXTENSION);
}
})
.marshal()
.jacksonxml(true)
.wireTap("{{trade-publisher.requestAuditDir}}" + "${header.inFileName}")
.to("{{trade-publisher.xsltFile}}")
.to("{{trade-publisher.outboundQueue}}")
.to("{{trade-publisher.responseAuditDir}}" + "${header.outFileName}")
.bean(txnService, "markSuccess")
.endDoTry()
.doCatch(Exception.class)
.bean(txnService, "markFailure")
.log(LoggingLevel.ERROR, "EXCEPTION: ${exception.stacktrace}")
.end();
TradePublisherRouteTest.java
#ActiveProfiles("test")
#RunWith(CamelSpringBootRunner.class)
#SpringBootTest(classes = TradePublisherApplication.class)
#MockEndpoints
public class TradePublisherRouteTest {
#EndpointInject(uri = "{{trade-publisher.outboundQueue}}")
private MockEndpoint mockQueue;
#EndpointInject(uri = "{{trade-publisher.sourceEndpoint}}")
private ProducerTemplate producerTemplate;
#MockBean
TradeService tradeService;
private List<Transaction> transactions = new ArrayList<>();
#BeforeClass
public static void beforeClass() {
}
#Before
public void before() throws Exception {
Transaction txn = new Transaction("TEST001", "C001", "100", "JPM", new BigDecimal(100.50), new Date(), new Date(), 1000, "P");
transactions.add(txn);
}
#Test
public void testRouteConfiguration() throws Exception {
Mockito.when(tradeService.searchTransaction()).thenReturn(new Data(transactions));
producerTemplate.sendBody(transactions);
mockQueue.expectedMessageCount(1);
mockQueue.assertIsSatisfied(2000);
}
Please correct me if i am doing something wrong!
I'm trying to run a unit test on inserting records to datastore using Objectify but I'm getting java.lang.NullPointerException: No API environment is registered for this thread every time I run the test or even trying to run it on Dev server.
Create an instance of LocalServiceTestHelper initializing LocalDatastoreServiceTestConfig and call setup() before doing any Objectify calls :
public class LocalDatastoreTest {
private final LocalServiceTestHelper helper =
new LocalServiceTestHelper(new LocalDatastoreServiceTestConfig());
#Before
public void setUp() {
helper.setUp();
}
#After
public void tearDown() {
helper.tearDown();
}
private void doTest() {
// Objectify calls will work here.
}
}
See also https://cloud.google.com/appengine/docs/java/tools/localunittesting
I'm very new with Apache Camel. I can't get the simplest Camel example working. Here is the code:
public class CamelFE {
public static void main(String[] args) {
CamelContext cc = new DefaultCamelContext();
cc.addRoutes(new RouteBuilder() {
#Override
public void configure() throws Exception {
System.out.println("Go!");
from("file://Users/Foo/Desktop/IN")
.to("file://Users/Foo/Desktop/OUT");
});
}
cc.start();
cc.stop();
}
Both directories exists, in the from one there is one simple file, helo.txt. The route starts and Go! message is displayed but no file was moved to the to directory. What am I missing?
Edit:
this is the console output.
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4j: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details
Go!
I'm guessing you're using Windows, since you have references to Users/.../Desktop. If that's the case, your file syntax is slightly off. Rather than file://Users/Foo/Desktop, you should have file:///Users/Foo/Desktop.
You also need to allow enough time for the processing to occur before the application terminates. You might add a Thread.sleep. Note that in a web application, this wouldn't be an issue as the app stays running.
public class CamelFE {
public static void main(String[] args) throws Exception {
CamelContext cc = new DefaultCamelContext();
cc.addRoutes(new RouteBuilder()
{
#Override
public void configure() throws Exception {
System.out.println("Go!");
from("file:///Users/Foo/Desktop/IN").to("file:///Users/Foo/Desktop/OUT");
}
});
cc.start();
Thread.sleep(10000);
cc.stop();
}
}
I'm playing with GAE hooks and trying to follow Nick's blog post. But apparently it's somewhat outdated because it doesn't have implementation of makeAsyncCall which exists in my GAE SDK 1.6.1.
Here is snippet of my code
public class MultiTenantHook implements Delegate
{
#Override
public Future<byte[]> makeAsyncCall(final Environment env, final String pkgName, final String method, final byte[] request, ApiProxy.ApiConfig config)
{
Callable<byte[]> callable = new Callable<byte[]>()
{
#Override
public byte[] call() throws Exception
{
return makeSyncCall(env, pkgName, method, request);
}
};
FutureTask<byte[]> task = new FutureTask<byte[]>(callable);
return task;
}
}
This method is being called but returned Future<> is never used by GAE. call() method of inner class is never executed.
Do you know how to make it work?