I recently added a couple of features to TestNG that make testing
asynchronous code (such as JMS) very easy:  parallelism and time-outs.

Parallelism instructs TestNG to run your test
methods in different threads. 
The trick here is that some of these test methods depend on each other, so these
methods
will obviously be invoked sequentially in the same thread.  But all the
other test methods that do not depend on anything else will be run in a separate
thread, picked from a pool (which size is configurable).  Here is an example
configuration:

<suite name="Main Suite" parallel="true" thread-count="10">
<test name="First Test">
<classes>
<class name="test.Test1" />
<class name="test.Test2" />
</classes>
</test>
</suite>

After the run, you can use the
chronological view to see which methods were
invoked and in what threads they ran.

Another interesting feature is time-outs, where you can indicate that a
specific test method is expected to return within a certain number of
milliseconds.  If it fails to do so, it will be interrupted and marked as a
FAIL with a TimeOutException.

With that in mind, testing JMS (or asynchronous code) becomes trivial. 
For example, imagine that our application posts a message on a JMS topic
whenever an error condition arises.  Here is how you could test it:

public class ErrorMessageTest implements MessageListener {
// Used to block
private Object m_done = new Object();
@Test(timeout = 5000 /* 5 seconds */)
public verifyErrorMessageGetsPosted() {
// register this class as a listener on the error topic
// create the error condition
// wait for completion
m_done.wait();
}
// implements MessageListener
public void onMessage(Message msg) {
if ( /* msg contains what we expect */) {
m_done.notify();
}
// else, do nothing, wait for another message
// or for the time-out to kick-in
}
}

This code is fairly self-explanatory and it leaves the dirty part (handling the
time-out and multi-threading issues to the testing framework).