Is it possible to write Unit Tests when a Scout BlockingCondition is involved?
Since there is no user interaction, the BlockingCondition seems never to be resolved.
This happens especially when testing a menu which starts a form.
Yes, you can either schedule a ClientAysncJob that - after a delay - schedules another ClientSyncJob to resolve the lock by invoking the ui facade methods of the message box.
Or you can - for testing reasons - create a custom IExceptionHandlerService instance with @Priority(1000) and register it using TestingUtilty. That way you can even use error handling info in your plugin unit tests.
If you are using Scout's JUnit based testing support by annotating your unit tests with @RunWith(ScoutClientTestRunner.class) on client side and @RunWith(ScoutServerTestRunner.class) on server side, you are already running within a scout job. Typically, it is not required to start any other jobs. Here is an overview of Scout's testing support: www.eclipse.org/forums/index.php/t/262115/
Could you give us more details if you have another use case?
The IExceptionHandlerService referenced by ivan.motsch is already registered by Scout's testing support. The SctouClientTestRunner and the ScoutServerTestRunner are both dynamically registering an IExceptionHanlderService with priority 1000. It wraps ProcessingExceptions into RuntimeExceptions which are unwrapped and rethrew later on so that the JUnit test fails.
@kid liang, which language do you prefer -- we try to help.
Possible deadlocks when running the tests is a known issue and very annoying. Unfortunately the scout testing framework does not provide a solution for this problem yet. Until there is one you can use the following helper functions resp. create similar ones for your specific case. Basically you need to register a ChangeListenerEx and release the waitFor by closing the form when it gets blocked.
Hope that helps.
Claudio
/**
* If clicking a button leads to a lock because execClickAction contains waitFor() no code can be executed until the
* lock is released.
* With this function it is possible to execute code just before the lock is installed.
* <p>
* The caller must ensure that the lock gets released (f.e. by canceling the form at the and of the runnable).
* </p>
*
* @param button
* @param runnableAfterButtonClick
* @throws ProcessingException
*/
public static void clickBlockingButton(IButton button, Runnable runnableAfterButtonClick) throws ProcessingException {
TestingExceptionHandlerService exceptionHandler = new TestingExceptionHandlerService();
List<ServiceRegistration> regs = TestingUtility.registerServices(Activator.getDefault().getBundle(), 1000, exceptionHandler);
WaitForListener waitForListener = new WaitForListener(runnableAfterButtonClick);
ClientJob currentJob = (ClientJob) ClientJob.getJobManager().currentJob();
currentJob.addJobChangeListenerEx(waitForListener);
try {
//click button which opens the form and blocks with waitFor()
button.doClick();
exceptionHandler.assertNoException();
exceptionHandler.clear();
}
finally {
currentJob.removeJobChangeListenerEx(waitForListener);
TestingUtility.unregisterServices(regs);
}
}
public static <T extends IForm> T findLastAddedForm(Class<T> formType) {
T[] forms = ClientSession.get().getDesktop().findForms(formType);
if (forms == null || forms.length == 0) {
return null;
}
return forms[forms.length - 1];
}
private static class WaitForListener extends JobChangeAdapterEx {
private Runnable runnable;
public WaitForListener(Runnable runnable) {
this.runnable = runnable;
}
@Override
public void blockingConditionStart(IJobChangeEvent event) {
try {
runnable.run();
}
catch (Throwable t) {
//Exception would be catched by the listener so it is necessary to use the exception service in order to make the test fail.
SERVICES.getService(TestingExceptionHandlerService.class).handleException(new ProcessingException("", t));
}
}
}
/**
* Use this custom exception handler service to catch all exceptions (message boxes) and check for specific events
*/
public class TestingExceptionHandlerService extends AbstractService implements IExceptionHandlerService {
private ProcessingException m_lastException;
@Override
public void handleException(ProcessingException t) {
if (t != null) {
String message = t.getStatus().getMessage().replaceAll("\\s+", " ").trim();
System.out.println("consuming: " + message);
}
m_lastException = t;
}
public void assertNoException() {
if (m_lastException == null) {
return;
}
if (m_lastException.getCause() instanceof AssertionError) {
throw (AssertionError) m_lastException.getCause();
}
throw new RuntimeException(m_lastException);
}
/**
* similar means all whitespace is replaced by one space and all text is lower case for compare
*/
public void assertLastExceptionIsSimilarTo(Class<?> type, String message) {
Assert.assertNotNull(m_lastException);
Assert.assertEquals(type, m_lastException.getClass());
String expected = m_lastException.getStatus().getMessage().toLowerCase().replaceAll("\\s+", " ").trim();
String actual = message.toLowerCase().replaceAll("\\s+", " ").trim();
Assert.assertEquals(expected, actual);
}
public void clear() {
m_lastException = null;
}
}