Hi there AppWorks fans,
Welcome to a new installment of AppWorks tips.
In my daily job I faced an issue while writing JUnit tests in Java on customized components for AppWorks. This post is just a share of the knowledge I gained after solving the issue. We will dive into the first steps of building a new ‘maven’ project which calls some AppWorks specific code. We want to mock specific calls to our platform, so our unit-tests can run on their own without the need of our AppWorks platform. For this mocking we use the (Power)Mockito libraries to support our activities. Nice stuff to play around with and this post brings a heads start on this topic…
Let get right into it…
This time it’s not needed to have our platform up and running as we will “mock” our calls with Mockito and the more specific PowerMockito. We don’t dive into the principles behind these 2 libraries as you can read all about it on those websites, but we will show you how to use both libraries during the creation of your unit tests for the created Java code.
Time to start a project from scratch…And with ‘a project’ I mean a Java coded ‘Maven’ project! These type of projects can be used when you would like to create something totally unsupported by the platform, but still needs to integrate with the platform. In my case it was Java code to create a new Enterprise Information System connector which makes it possible to integrate an external system into the AppWorks platform, but other types of customization that require Java code are also possible like the described UDDI interceptor example. When you do a search on the keyword ‘implements’ in the ‘Advanced Development Guide’ PDF you will also see some interesting customizations we can do on the platform…Like for example:
- Dispatch (re-route) tasks more intelligent:
com.cordys.notification.customdispatch.CustomTaskDispatcher
%CORDYS_HOME%/components/notification/notification.jar
- To add more dynamic functionality to an xForm:
com.eibus.xforms.persistence.IXFormsDefinition
%CORDYS_HOME%/components/xfruntime/xfruntime.jar
- Create an xForm interceptor
com.eibus.applicationconnector.xforms.Interceptor
%CORDYS_HOME%/components/xfruntime/xfruntime.jar
- To enhance the connection object used in the WS-AppServer connection:
com.cordys.xqy.connection.IConnectionCustomizer
%CORDYS_HOME%/components/dbconnection/dbconnection.jar
- Enables developers to customize business logic in business processes:
com.cordys.bpm.event.ProcessEventListener
%CORDYS_HOME%/components/bpmengine/bpmengine.jar
- Customize WS-AppServer SOAP fault messages before they are rendered in runtime
com.cordys.cpc.bsf.event.ITransactionErrorListener
%CORDYS_HOME%/components/wsappserver/wsappserver.jar
- Participate in the WS-AppServer transaction (Bus Service Framework):
com.cordys.cpc.bsf.connector.IOnBsfConnector
%CORDYS_HOME%/components/wsappserver/wsappserver.jar
- Controlling the SOAP response XML data:
com.eibus.applicationconnector.java.Tupable
%CORDYS_HOME%/components/wsappserver/wsappserver.jar
- Interact with the logging framework
com.eibus.soap.ApplicationTransaction
%CORDYS_HOME%/components/esbserver/esbserver.jar
- Usage of the problem registry
com.eibus.management.IProblemResolver
%CORDYS_HOME%/components/managementlib/managementlib.jar
An interesting list to play around with in the future…It’s on the backlog!
Let’s go back on track…
The creation of our Java ‘Maven’ project
To make it all pure and understandable as possible we don’t use any fancy IDE, but we use the good old command prompt in Windows…Windows? Yes, I know we have our platform VM normally running on CentOS (Linux), but our unit tests will run on Windows as they will be executed when we generate a Java Archive (JAR) library which (eventually) needs to be deployed somewhere on our AppWorks platform.
Let’s start the ‘Command Prompt’ and give the following command: mvn --version
1 | Apache Maven 3.6.1 (...) |
If this is not working, you need to install Apache Maven from here
What I also saw is a conflict in Java version in a later stage of crafting this post with an error like this:
class file has wrong version 55.0, should be 52.0
. Make sure you use the same Java version for your project as the (supported) Java version installed on your AppWorks server. For AppWorks 21.1 this will be Java 11! The Java version used by Maven is the one available in the environment variable ‘JAVA_HOME’.For my current command prompt session I will set it like this:
set JAVA_HOME=C:\Program Files\Java\jdk-11.0.8
Next…Go to your favorite folder on your machine and initialize our first Maven project:
mvn archetype:generate -DgroupId=nl.bos -DartifactId=helloworld -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.4 -DinteractiveMode=false
This command creates a new folder ‘helloworld’ with a ‘pom.xml’ where we can configure our dependencies and other project specific settings. We also get a ‘src’ folder where we see a split in a ‘main’ folder (for our real code) and ‘test’ folder (for our unit tests).
After this project generation we need to jump into the ‘helloworld’ folder (where the pom.xml file can be found) and give the command mvn clean package
. It will clean our project and package it to a JAR library saved in the ‘target’ directory of our project…This last library is the file to be deployed on our AppWorks server (on whatever location that is required). During this command you also see the triggering of the unit tests!
Nice…Next step…
Call our first AppWorks specific code
Real simple…Update the ‘App.java’ code, so it looks like this (\helloworld\src\main\java\nl\bos\App.java). You can edit it with a simple notepad editor for now:
1 | package nl.bos; |
This LOGGER object will make sure we can write data to the AppWorks platform specific logfiles…Trust me on that one! 😇
OK…mvn clean package
OMG (as my kids would say!)…That’s a failure!!…Our first missing dependency for the class ‘CordysLogger’!
1 | package com.eibus.util.logger does not exist |
After a quick search on my VM (Yes…I sneaky started the thing in the background to provide you with the correct information) with a command like this: sudo find /opt/opentext/AppWorksPlatform/defaultInst/ -type f -name '*.jar' -print0 | xargs -0 -I '{}' sh -c 'jar tf {} | grep CordysLogger.class && echo {}'
This command tells me what library includes the missing class for our project with this result:
/opt/opentext/AppWorksPlatform/defaultInst/components/managementlib/managementlib.jar
Let’s download this file from our VM and save it somewhere nice (like a special ‘libs’ directory somewhere locally on your machine…C:\CORDYS_21_1\components\managementlib
!?)
When you don’t have any access to a machine it’s also possible to get those files from the installation Linux package downloaded from the support site…Have a look (with 7-ZIP) for example in this file in this location:
\opentext-appworks-suite-21.1-linux\OpenTextAppWorksPlatform\OpenText_AppWorks_Platform_21.1.bin\$ROOT_BUILD_FOLDER$\jar\
Next is to add this library as dependency into our pom.xml with a dependency section like this:
1 | <dependencies> |
Maven (by default) will search for this dependency in the central maven dependency server, but after a rebuild these specific AppWorks dependencies will not be found here! Why? Because they are domain specific and not meant to be published in the open interwebs!
The error: Could not resolve dependencies for project nl.bos:helloworld:jar:1.0-SNAPSHOT: Could not find artifact com.cordys:managementlib:jar:21.1 in central
So, now what? Well, we make sure we install our AppWorks dependencies on a local maven dependency repository as this is the next location where it will search for dependent libraries. To install our downloaded JAR in a local maven repo we can execute this command: mvn install:install-file -Dfile="C:\CORDYS_21_1\components\managementlib\managementlib.jar" -DgroupId=com.cordys -DartifactId=managementlib -Dversion=21.1 -Dpackaging=jar
After a mvn clean package
we should be fine again!
Sometimes you need an extra flag in Maven to explicitly update dependencies if they still are not found:
mvn clean package -U
We should be fine??…NOT!!
class file for com.eibus.basicutil.Logger not found
Ahaaaa…Our next missing dependency…Well…You know the drill…right?
Steps:
- Grab the JAR file called ‘basicutil.jar’
- Install in local maven repo:
mvn install:install-file -Dfile="C:\CORDYS_21_1\components\basicutil\basicutil.jar" -DgroupId=com.cordys -DartifactId=basicutil -Dversion=21.1 -Dpackaging=jar
- Add dependency in pom.xml
mvn clean package -U
GREAT…BUILD SUCCESS
!! 😅
Our first JUnit test
Let’s update the already generated test class ‘AppTest.java’
1 | package nl.bos; |
And do a mvn clean package
…
Error again: AppTest.constructorTest:12 » NoClassDefFound org/apache/log4j/Level
That’s a familiar class…With a familiar dependency in our ‘pom.xml’
1 | <!-- https://mvnrepository.com/artifact/log4j/log4j --> |
As this is a maven central management dependency, we choose to grab it from there. Make sure to use the same version from our beloved platform! Another possibility is to download it again from our AppWorks server/software and install it in our local maven repository!?…Choices!?…Choices!?
Next errors after a clean/package:
1 | ERROR Could not find Log4jConfiguration.xml. C:\helloworld\null\config\Log4jConfiguration.xml |
Let’s solve this for once and always…As we probably require more dependencies for our platform!
Now we really need to spin up our VM and make a folder copy of these directories locally on our Windows machine (in my case C:\CORDYS_21_1
).
Now you also see why the creation of that directory structure happened in the first place for the earlier downloaded JAR files!
With this folder filled up with dependencies we also set a new environment variable: set CORDYS_HOME=C:\CORDYS_21_1
A new clean/package tells me the ‘Log4jConfiguration.xml’ is found now, but an error: AppTest.constructorTest:10 » NoClassDefFound com/eibus/xml/nom/Document
Again an AppWorks specific dependency…This time it’s the file: /opt/opentext/AppWorksPlatform/defaultInst/components/eibxml/eibxml.jar
Or better this file location: C:\CORDYS_21_1\components\eibxml\eibxml.jar
ORRRRR…even more “better”…this location: C:\CORDYS_21_1\cordyscp.jar
as this file has the correct reference to all the needed dependencies:
Let’s update our pom.xml with this particle (in the ‘build -> pluginManagement -> plugins’ element):
1 | <plugin> |
Let’s see…mvn clean package
Error again: AppTest.constructorTest:10 » UnsatisfiedLink no xmlForJava in java.library.pat...
hmmm…we’re getting closer and closer!
Time for another ‘pom.xml’ update…
1 | <plugin> |
Again mvn clean package -U
Damn…Same error again: AppTest.constructorTest:10 » UnsatisfiedLink no xmlForJava in java.library.pat...
But it finds the correct path…When I have a look in C:\CORDYS_21_1\lib
I see also this particular file ‘libxmlForJava.so’ passing by!
After some research I came to a solution…These ‘.so’ files are so-called ‘Shared Object’ files specific for Linux…And we’re on Windows…duh!…That’s why it’s not working! Don’t we need the Windows variant of these files? So-called ‘.dll’ files!?
Where to get them from as we only have a Linux VM available!?!?…Questions…Questions!
A dive (again) in this file (with 7-ZIP) from our downloaded software package provided me this information:\opentext-appworks-suite-21.1-linux\OpenTextAppWorksPlatform\OpenText_AppWorks_Platform_21.1.bin\$INSTALLER_BUILD_FOLDER$\libraries_zg_ia_sf.jar\
Well…what about the Windows software package?? hmmmm…It’s getting more and more interesting!!
Have a look in this file (with again 7-ZIP): opentext_appworks_platform_21.1_windows\OpenText_AppWorks_Platform_21.1.zip\InstallerData\Disk1\InstData\Resource1.zip\$CORDYS_BUILD_FOLDER$_39b648a3b588_zg_ia_sf.jar\lib\
YES…I renamed the ‘.exe’ to a ‘.zip’ to make it openable from 7-ZIP!
That’s our ‘Hello world’ moment! 😜
Well…where are we waiting for…Extract those files into: C:\CORDYS_21_1\lib
…
mvn clean package
Hmmm…java.lang.UnsatisfiedLinkError: C:\CORDYS_21_1\lib\xmlForJava.dll: Can't find dependent libraries
So, even these files have a dependency!?
We can easily solve this one by updating the ‘PATH’ variable with this entry: C:\CORDYS_21_1\lib
set PATH=%PATH%;%CORDYS_HOME%\lib
Let’s see…mvn clean package
…
Ohhh mama…GREAT…BUILD SUCCESS
!! 😅
One last question…Do we still need all those introduced dependencies in our pom.xml file after we point to all those variables!?
Well, not all of them…I end up with just these two (and removed the common log4j dependency as it will be found from the ‘cordyscp.jar’ on this location C:\CORDYS_21_1\ext\log4j-1.2.15.jar
):
1 | <dependency> |
Time for a beer before we continue…🍺
Introduce (Power)Mockito
Before we start with Mockito let’s craft a small piece of code which requires the usage of a running AppWorks platform! Like checking some user credentials for example with an extra method like this in our ‘App.java’ file:
1 | public static boolean check(String username, String password) throws InvalidCredentialsException, AuthenticationException { |
For the 3 new objects ‘Authenticator’, ‘CARSAuthenticator’ and ‘UsernamePasswordCredentials’ we need 1 new platform dependency:
mvn install:install-file -Dfile="C:\CORDYS_21_1\components\esbclient\esbclient.jar" -DgroupId=com.cordys -DartifactId=esbclient -Dversion=21.1 -Dpackaging=jar
1
2
3
4
5
6 <dependency>
<groupId>com.cordys</groupId>
<artifactId>esbclient</artifactId>
<version>21.1</version>
<scope>provided</scope>
</dependency>Also don’t forget to use the imports of the libraries in the Java class:
1
2
3
4
5
6 import com.eibus.security.authentication.AuthenticationException;
import com.eibus.security.authentication.Authenticator;
import com.eibus.security.authentication.CARSAuthenticator;
import com.eibus.security.identity.InvalidCredentialsException;
import com.eibus.security.identity.UsernamePasswordCredentials;
import com.eibus.util.logger.CordysLogger;
Our unit test? Well, call the method from a new unit test:
1 |
|
With the correct imports:
1
2 import com.eibus.security.authentication.AuthenticationException;
import com.eibus.security.identity.InvalidCredentialsException;
mvn clean package
starts to fail again! Why?
The use of MPC is enabled, but it seems that the 'microprofile-config-api-*.jar' and/or 'geronimo-config-impl-*.jar' is not on the classpath
OK??? now what?
Add those dependencies in the pom.xml?
1 | <!-- https://mvnrepository.com/artifact/org.eclipse.microprofile.config/microprofile-config-api --> |
After clean/package (with -U to update dependencies) I get the next error: NoClassDefFound org/yaml/snakeyaml/error/YAML...
Dependency!
1 | <!-- https://mvnrepository.com/artifact/org.yaml/snakeyaml --> |
java.lang.NoClassDefFoundError: javax/transaction/Synchronization
Dependency again…
1 | <!-- https://mvnrepository.com/artifact/org.apache.tomee/javaee-api --> |
YES….There it is again…our ALMOST green flag after a mvn clean package -U
💚
Why almost? Well, our ‘remote’ AppWorks environment isn’t found for some reason as it searches for some local instance!? Have a look in the C:\CORDYS_21_1\Logs
directory where you will find more information, and the stack trace on what is happening. But!…When I follow all the information from above directly on my AppWorks VM it works fine, and I really get a BUILD SUCCESS
again.
Updates in the backend after a deep dive on this ‘remote’ problem (and took too much of my time!):
An update in the ‘hosts’ file in Windows:
192.168.56.107 appworks.mydomain.com appworks
as I see the configuration files point to the hostname of my image…This is indeed correct as I got a copy from my VM!Updates in:
C:\CORDYS_21_1\config\wcp.properties
1
2
3
4
5 #Watch those forward slashes! Or use double slash -> C:\\CORDYS_21_1\\...
C:/CORDYS_21_1/certificates/keystore/platform_saml2.p12 =
C:/CORDYS_21_1/certificates/truststore/CordysTrustStore.jks =
C:/CORDYS_21_1/certificates/keystore/platform_monitor.p12 =
appworks =I also saw information retrieved from this file:
C:\CORDYS_21_1\config\DMZ.xml
- This file contains some
bussoapprocessorconfiguration
elements where also hostnames are defined…Have a search!The final attempt (but still a failure!) was an update in the
wcp.properties
file with these 2 lines:
1
2 SYNCUP_REMOTE_HOST=appworks
SYNCUP_REMOTE_PORT=8432
Ok…Time to continue as we don’t want to spend more time on calling a remote AppWorks environment, we want to be able to run our unit tests without the use of a platform!
…
Time to add ‘Mockito’ into the game…As we now run code directly against the platform, but for a unit test we don’t want to be dependent on an AppWorks platform (That’s more the responsibility of an integration test, but that’s not the scope for this post).
So, first things first…Shut down your VM again and let’s update our pom.xml with the ‘Mockito’ maven dependencies. I also include the ‘PowerMockito’ dependencies as they are an addition to ‘Mockito’ itself. I won’t go into the difference between both frameworks as you can look it up with a Google search. With this post I just want you a quick start on writing unit tests which calls AppWorks code without the need of a running platform!
1 | <!-- https://mvnrepository.com/artifact/org.mockito/mockito-core --> |
Important here is the use of the same version (2.0.7) for both the ‘PowerMockito’ dependencies!
Next step is to update our ‘AppTest.java’ file to make sure our executed tests run under the ‘PowerMockito’ flag, and we also make sure our ‘App.java’ file gets a preparation for it, so we can do all kinds of fancy mocking stuff with it!
1 | .class) (PowerMockRunner |
Now we update the test where we do platform dependent call like our App.check("awdev", "admin")
method. Let’s update this method like this:
1 |
|
This is the set off imports included in this test class (for your verification):
1
2
3
4
5
6
7
8
9 import com.eibus.security.authentication.CARSAuthenticator;
import com.eibus.security.identity.UsernamePasswordCredentials;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
What are we doing here?
Well…real simple, we start by telling our test when our ‘App.java’ creates a new instance of the CARSAuthenticator
class, we return a mock object. When we call a specific method (our authenticate(...)
method), we would like to get a direct return value of ‘true’ which skips all the back-end of our platform…Isn’t this a nice thing!?
So, with our VM shutdown, this test will be valid! After a mvn clean package -U
it is!
Nice, but how do we know our test really called the authenticate(...)
method? Good question and for this I also would like to add an extra line to our test to verify this!
The end result where our test will look like this:
1 |
|
With still a green flag while building a new JAR with mvn clean package
!
I also saw this error passing by:
IllegalAccess class javax.naming.spi.NamingManage
. This all has to do with the module structure in Java 11 and PowerMockito which has some issues with it…The solution?Add this annotation on top of the AppTest class:
@PowerMockIgnore({"javax.naming.*"})
That’s it…a green “DONE” for our first steps on unit testing with (Power)Mockito where we make it possible to call AppWorks specific code without the need of a running platform. We also learned how to build a new project with Maven for our future development activities and made the required dependencies available on our local machine. I would say…Have a great development week-end, and I see you in the next post on a new AppWorks Tips installment.
Ohw yeah…To mock an object it’s also possible to use the @Mock annotation on a field variable like this replaceable line:
1
2
3 //CARSAuthenticator mockCARSAuthenticator = Mockito.mock(CARSAuthenticator.class);
CARSAuthenticator mockCARSAuthenticator;
Don’t forget to subscribe to get updates on the activities happening on this site. Have you noticed the quiz where you find out if you are also “The AppWorks guy”?