XPages series #10: Running JUnit tests on XPages code
Karsten Lehmann 7 February 2011 09:03:00
The 10th article of the XPages series deals with the first sample I demo'ed for the Lotusphere session BP212 - Deep Dive into IBM XPage Expression Language Syntax:It shows how you can develop and test most of your XPages code outside of DDE.
So why should you development code outside of DDE?
1. Workaround for classloader issues
Well, you can experience a very nasty caching behaviour when you develop Java code in the Java perspective of DDE and test it live in an XPages application on the client. This might result in ClassCastExceptions like "com.company.packagename.MyClass cannot be cast to com.company.packagename.MyClass", which is caused by the JSF servlet: It still has a cached instance of the first bean in memory, created with one classloader while it tries to run the XPages application after code changes with a new classloader which is incompatible with the first one used.
This behaviour is discussed in an article in the Lotus Notes and Domino Application Development Wiki and IBM recommends to restart the http task in this case (only a viable option if you develop on a local Domino server), because this shuts down and restarts the JVM used to load the classes. Unfortunately this is not an option for development in the Notes Client, since you really don't want to restart your Notes Client/DDE every time you change code.
In addition to classloader issues, the JSF runtime also seems to cache Java classes and XPages intermittently, a problem that I faced just recently when I worked on the demos for BP212 and tested them in the client.
2. Version control for source code
Another reason why you should develop code outside of DDE is that you can use version control systems like Subversion or CVS. With 8.5.2 and an additional source control plugin from OpenNTF this aspect is less important than before, but I still prefer to have my code outside of a virtual file system that is stored in an NSF. I just don't trust that stuff and don't want to lose my code, e.g. if DDE does a rebuild on two developer machines and then they replicate with the same db instance on a server.
3. Use code in other Non-XPages projects
We do not only develop XPages apps, we develop libraries to be used in agents, standalone Eclipse RCP/Swing applications, server-side OSGi plugins and for many other areas. That's why we want to share most of the code between multiple development projects.
4. Run unit tests on the code for quality checks
To ensure that our code still produces the expected results after a code change, we use JUnit test cases on a manual or scheduled basis.
And that is exactly what this sample is about.
Workspace content
The download archive for this blog article contains two directories: a sample XPages application and an exported Eclipse workspace.
The XPages application consists of a simple XPage:
Here is the source code:
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
<xp:span style="font-size:20pt">Current User Information</xp:span>
<xp:br></xp:br><xp:br></xp:br>
<xp:span style="font-size:16pt">
Common Name:
<xp:br></xp:br>
<xp:text escape="true" id="commonName" value="#{currentUser.commonName}"
style="color:rgb(0,128,255)"></xp:text>
<xp:br></xp:br>
<xp:br></xp:br>
<xp:span style="font-size:16pt">Abbreviated Username:</xp:span>
<xp:br></xp:br>
<xp:text escape="true" id="abbrName" value="#{currentUser.abbreviatedName}"
style="color:rgb(0,128,255)"></xp:text>
</xp:span>
</xp:view>
All it does is read the name information of the current user from a managed bean "currentUser" and display it on screen.
The managed bean class is not directly part of the NSF, but has been added in a "lib" folder below the WebContent/WEB-INF folder as a JAR file and then added to the classpath of the NSF project in DDE (after that, it's not visible in the "lib" folder anymore, but below "Referenced Libraries").
After you import the projects of the "workspace" directory into your personal Eclipse workspace, it should look like this:
Please note that you might get compile errors and need to change the path of the referenced "Notes.jar" file in the com.ls11.dominohelper project to fit your Notes configuration.
There are four projects in the workspace:
com.ls11.dominohelper
- The project contains helper classes to execute a piece of code in the context of a Notes session (see below for details)
- This project contains the bean implementation to read the current username
- We use the ANT script of this project to create the JAR file with the necessary content to work in an XPages app
- Finally this project contains a JUnit test case to check that our bean is working correctly
Running the same code standalone and from an XPages request
Let's dive into the class CurrentUserInfo.java. The method .getCommonName() looks like this:
/**
* Returns the common name of the current user
*
* @return common name
*/
public String getCommonName() {
if (m_commonName==null) {
IDominoCallable<String> callable=new IDominoCallable<String>() {
@Override
public String call(Session session) throws NotesException {
Name currName=session.createName(session.getUserName());
String commonName=currName.getCommon();
currName.recycle();
return commonName;
}
};
try {
m_commonName=DominoExecution.run(callable).get();
} catch (InterruptedException e) {
throw new RuntimeException("Could not read username info", e);
} catch (ExecutionException e) {
throw new RuntimeException("Could not read username info", e);
}
}
return m_commonName;
}
Now that looks interesting: We are using an
IDominoCallable
implementation to retrieve the common name part of the username. We do this in order to run the same code from an XPages environment and from a standalone application. In the
DominoExecution
there's some magic that detects whether it gets executed from XPages code or not. In standalone mode,
DominoExecution
currently launches a new NotesThread
for every call, but that could easily be changed to have a permanent thread and just feed it with the IDominoCallable's
one after another. The run method of DominoExecution returns a
Future
object of Java's concurrency framework. Calling its .get() method let's the executing code wait for the result to be computed. In XPages mode, the
IDominoCallable
is executed synchronously, because the code is already running in a Domino enabled thread. Running JUnit in Eclipse
The project com.ls11.externalcodesample.test contains a JUnit test case that checks whether the methods of the bean are working correctly: For example it ensures that abbreviated name and common name are both not null and compares the first part of the abbreviated name with the common name.
/**
* Check that common and abbreviated name
*/
public void testCommonNameAndAbbrNameConsistent() {
String commonName=m_currUserInfo.getCommonName();
String abbrName=m_currUserInfo.getAbbreviatedName();
//the values cannot be null
assertNotNull(commonName);
assertNotNull(abbrName);
int iPos=abbrName.indexOf("/");
assertTrue((iPos ! = - 1) && (iPos ! = 0) );
String firstAbbrNamePart=abbrName.substring(0, iPos);
assertEquals(commonName, firstAbbrNamePart);
}
You can launch the test case by first creating a JUnit run configuration:
On the Environment tab, please add a variable "PATH" that contains the path to your Lotus Notes program directory:
Now you can execute the test case and get the following result:
The method testCommonNameAndAbbrNameConsistent could be executed without errors, but calling testOrganisation led to an error.
It checks that CurrentUserInfoTest.getOrganisation() is not null, but unfortunately, it seems like we forgot the method's implementation:
public String getOrganisation() {
// TODO Auto-generated method stub
return null;
}
After fixing that, the JUnit succeeds:
Now that we know that the bean works, we can package everything by right clicking on the ANT script of project com.ls11.externalcodesample.build, choose "Run As/Ant Build", then do a refresh on the "lib" folder and replace the JAR in the XPages application database with the new one.
That's it!
Here is the download link for the archive:
LS11-BP212-JUnit.zip
- Comments [7]