20 May 2012

Mocking the Eclipse Extension Registry

Some classes of a RCP application might make use of Extension Points, calling Platform.getExtensionRegistry() and evaluating the returned configuration elements. It is possible to mock (in fact stub) the default registry provider by calling RegistryFactory.setDefaultRegistryProvider() with a registry implementation. By using a fake registry some PDE JUnit test cases can be converted to plain JUnit tests, which speeds up their execution.

A Changing Registry Factory
The default registry provider can only be set once. So we need to register a wrapper, which delegates to a RegistryFactory so it can be changed among tests.
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IExtensionRegistry;
import org.eclipse.core.runtime.RegistryFactory;
import org.eclipse.core.runtime.spi.IRegistryProvider;

public class ChangingRegistryFactory {

  private static IRegistryProvider registryProvider;
  private static boolean defaultRegistryHasBeenSet;

  public static void setDefaultRegistryProvider(IRegistryProvider provider)
                                                       throws CoreException {
    setRegistryProvider(provider);
    synchronized (ChangingRegistryFactory.class) {
      setDefaultRegistryProviderOnce();
    }
  }

  private static void setRegistryProvider(IRegistryProvider provider) {
    registryProvider = provider;
  }

  private static void setDefaultRegistryProviderOnce() throws CoreException {
    if (!defaultRegistryHasBeenSet) {
      setDefaultRegistryProvider();
      defaultRegistryHasBeenSet = true;
    }
  }

  private static void setDefaultRegistryProvider() throws CoreException {
    RegistryFactory.setDefaultRegistryProvider(new IRegistryProvider() {
      public IExtensionRegistry getRegistry() {
        return registryProvider.getRegistry();
      }
    });
  }

}
PlatformAn Extension Point Mockery
Using EasyMock or any other mock framework we can create a mock registry and register it to the RegistryFactory (in fact to the ChangingRegistryFactory). To hide the mocking details we provide a specific builder like API in the helper class ExtensionPointMockery which helps mocking Extension Points:
import static org.easymock.EasyMock.*;

public class ExtensionPointMockery {

  private final IExtensionRegistry registry = createRegistry();

  @SuppressWarnings("unused")
  public ExtensionPointMockery() throws CoreException {
    // allow CoreException to be thrown from initialiser blocks
  }

  private IExtensionRegistry createRegistry() {
    return createMock(IExtensionRegistry.class);
  }

  public IConfigurationElement confWith(Object specificInstance)
                                                       throws CoreException {
    IConfigurationElement confElement = createMock(IConfigurationElement.class);
    expect(confElement.createExecutableExtension("class")).
          andReturn(specificInstance).anyTimes();
    replay(confElement);
    return confElement;
  }

  public IConfigurationElement mockFor(Class<?> type) throws CoreException {
    Object mockInstance = createNiceMock(type);
    replay(mockInstance);
    return confWith(mockInstance);
  }

  public void set(String key, IConfigurationElement... elements) {
    expect(registry.getConfigurationElementsFor(key)).
          andReturn(elements).anyTimes();
  }

  public void setAll(IConfigurationElement... elements) {
    expect(registry.getConfigurationElementsFor(anyObject(String.class))).
          andReturn(elements).anyTimes();
  }

  public void close() throws CoreException {
    replay(registry);

    IRegistryProvider provider = createMock(IRegistryProvider.class);
    expect(provider.getRegistry()).andReturn(registry).anyTimes();
    replay(provider);

    ChangingRegistryFactory.setDefaultRegistryProvider(provider);
  }

}
Usage
Its typical usage looks like
new ExtensionPointMockery() {{
  set("preferenceSettings", mockFor(IPreferenceSettings.class));
  set("filterManager", confWith(new FilterManagerDelegate()));
  set("postProcessor");
}}.close();
where confWith() puts the given object directly into the registry, simulating its contribution and mockFor() creates a "nice mock" (a fake) of the given interface and adds it to the configuration. The call of set() without any parameter initialises the extension registry for the given name but without any contributions. Finally close() creates the mock registry and registers it to the platform.

Happy mocking!

2 comments:

Gowrisankar said...

However if I mock "return createMock(IExtensionRegistry.class);" I am getting securityException.
Is there any solution for this? is it working code?

Peter Kofler said...

Sure this is (was) working code. It worked as listed above within Eclipse 3.5.

Is your SecurityException related to signed jars? Then it might be a problem with the underlying CGLib, see http://stackoverflow.com/questions/28579999/securityexception-when-running-plain-junit-mockito-in-eclipse-rcp-project (The link is for Mockito, but I guess the problem and possible fix is the same.)

Also I am not sure about Eclipse 4+, it has never been tested there.