Provides mock helpers to register delegate/listener mocks while testing processes
Java
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
.mvn/wrapper
docs
src
.editorconfig #6 support throw exception on runtime Oct 2, 2014
.gitignore
.travis.yml fix(test) fixes #49, added coverage metering and reporting Feb 15, 2018
CONTRIBUTING.md
LICENSE
README.md
mvnw
mvnw.cmd
pom.xml

README.md

camunda-bpm-mockito

Build Status codecov Project Stats

Maven Central

‼️ With release 3.0.0 the groupId changed, it is now org.camunda.bpm.extension.mockito ‼️

simplify process mocking and testing

camunda-bpm-mockito is a community extension for the Camunda BPM process engine that aims to simplify and automate mocking of process applications.

Features:

  • Fluent mocking of query API - It is now very easy to mock a complex fluent query for the service API without any redundancy.
  • Fluent mocking of Listener and Delegate behavior - since delegate and listener methods are void, they can either modify process variables or raise an error. Instead of messing up with mockito's doAnswer() you can use these options with a fluent API.
  • Fluent mocking of Sub Processes - Sub process is able to wait for timer, set variable, wait for message, send message, throw exception or just do anything what you want.
  • Helpers for registering, retrieving and verifying mocks - convenience methods around Mocks.register().
  • Automatic mocking of all expressions and delegates in a process - without explicitly registering mocks, all instances are mocked by default, so no process will fail to run because a JUEL expression is using an unknown statement or identifier.

Get started

Just include camunda-bpm-mockito in the test scope of your project:

<dependency>
  <groupId>org.camunda.bpm.extension.mockito</groupId>
  <artifactId>camunda-bpm-mockito</artifactId>
  <scope>test</scope>
  <version>3.2.1</version>
</dependency>

Mocking of queries

Sometimes you want to test a Bean that uses the query API. Since the API is fluent, you would have to mock every single parameter call and let your service return the mocked query.

With the QueryMocks extension, you can do all this in just one line of code, see QueryMocksExample.java.

  public class QueryMocksExample {
    private final TaskService taskService = mock(TaskService.class);
    private final Task task = mock(Task.class);
    
    @Test
    public void mock_taskQuery() {
        // bind query-mock to service-mock and set result to task.
        final TaskQuery taskQuery = QueryMocks.mockTaskQuery(taskService).singleResult(task);

        final Task result = taskService.createTaskQuery().active().activityInstanceIdIn("foo").excludeSubtasks().singleResult();

        assertThat(result).isEqualTo(task);

        verify(taskQuery).active();
        verify(taskQuery).activityInstanceIdIn("foo");
        verify(taskQuery).excludeSubtasks();
    }
}

Mock Listener and Delegate behavior

Mocking void methods using mockito is not very convenient, since you need to use the doAnswer(Answer<T>).when() construct, implement your own answer and pick up the parameter from the invocation context. JavaDelegate and ExecutionListener are providing their basic fuctionality using void methods.
In general, when working with the Delegate and Listener interfaces, there are basically two things they can do from the point of interaction between the process execution: modify process variables and raise errors.

We can use this to test bpmn-processes without relying on the delegate implementation.

public class FluentJavaDelegateMockTest {

  private static final String BEAN_NAME = "foo";
  private static final String MESSAGE = "message";

  @Rule
  public final ExpectedException thrown = ExpectedException.none();

  @Test
  public void shouldThrowBpmnError() throws Exception {

    // expect exception
    thrown.expect(BpmnError.class);
    thrown.expectMessage(MESSAGE);

    DelegateExpressions.registerJavaDelegateMock(BEAN_NAME).onExecutionThrowBpmnError("code", MESSAGE);

    final JavaDelegate registeredDelegate = DelegateExpressions.getJavaDelegateMock(BEAN_NAME);

    // test succeeds when exception is thrown
    registeredDelegate.execute(mock(DelegateExecution.class));
  }
}

Easy register and verify mocks

In addition two of the well-known "Mocks.register()" hook, you now have the possibility to register fluent mocks directly:

 registerJavaDelegateMock("name")
 registerMockInstance(YourDelegate.class)

In the latter case, "YourDelegate" has to be annotated with @Named, @Component or @Service, depending on the injection framework you are using.

To verify the Mock execution, you can use

verifyJavaDelegateMock("name").executed(times(2));

Auto mock all delegates and listeners

With the autoMock() feature, you can register all Delegates and Listeners at once, without explicitly adding "register"-statements to your testcase. If you do need to specify behaviour for the mocks, you can still get the mock via "getRegisteredJavaDelegateMock" (for delegates).

@Test
@Deployment(resources = "MockProcess.bpmn")
  public void register_mocks_for_all_listeners_and_delegates() throws Exception {
    autoMock("MockProcess.bpmn");

    final ProcessInstance processInstance = processEngineRule.getRuntimeService().startProcessInstanceByKey("process_mock_dummy");

    assertThat(processEngineRule.getTaskService().createTaskQuery().processInstanceId(processInstance.getId()).singleResult()).isNotNull();

    verifyTaskListenerMock("verifyData").executed();
    verifyExecutionListenerMock("startProcess").executed();
    verifyJavaDelegateMock("loadData").executed();
    verifyExecutionListenerMock("beforeLoadData").executed();
  }

Delegate[Task|Execution]Fake

Unit-testing listeners and JavaDelegates can be difficult, because the methods are void and only white-box testing via verify is possible. But most of the times, you just want to confirm that a certain variable was set (or a dueDate, a candidate, ...).

In these cases, use the Delegate fakes. The implement the interfaces DelegateTask and DelegateExecution, but are implemented as plain, fluent-styled Pojos.

So to test if your TaskListener

TaskListener taskListener = task -> {
  if (EVENTNAME_CREATE.equals(task.getEventName()) && "the_task".equals(task.getTaskDefinitionKey())) {
    task.addCandidateGroup((String) task.getVariableLocal("nextGroup"));
  }
};

actually adds a candidateGroup that is read from a taskLocal variable on create, you can write a Test like this one:

@Test
public void taskListenerSetsCandidateGroup() throws Exception {
  // given a delegateTask 
  DelegateTask delegateTask = delegateTaskFake()
    .withTaskDefinitionKey("the_task")
    .withEventName(EVENTNAME_CREATE)
    .withVariableLocal("nextGroup", "foo");

  // when
  taskListener.notify(delegateTask);

  // then the candidate group was set
  assertThat(candidateGroupIds(delegateTask)).containsOnly("foo");

}
 

Mocking of external subprocesses

With ProcessExpressions.registerSubProcessMock() you can easily register a mocked process which is able to act with those behaviours:

  • onExecutionAddVariable ... the MockProcess will add the given process variable
  • onExecutionWaitForTimerWithDate ... the MockProcess will wait for the given date
  • onExecutionWaitForTimerWithDuration ... the MockProcess will wait until the given duration is reached
  • onExecutionSendMessage ... the MockProcess will correlate the given message (to all or a single process)
  • onExecutionWaitForMessage ... the MockProcess will wait for the given message
  • onExecutionRunIntoError ... the MockProcess will throw the given Throwable as RuntimeException
  • onExecutionDo ... the MockProcess will execute the given consumer

All of those methods could be combined on the fluent sub process mock builder.

The following example will e.g. register a process mock which does the following:

  1. Wait until the given message SomeMessage gets correlated to the mock
  2. Then wait until the given date waitUntilDate is reached
  3. After this, a process variable foo is set with a value of `bar
    ProcessExpressions.registerSubProcessMock(SUB_PROCESS_ID)
      .onExecutionWaitForMessage("SomeMessage")
      .onExecutionWaitForTimerWithDate(waitUntilDate)
      .onExecutionSetVariables(createVariables().putValue("foo", "bar"))
      .deploy(rule);

More examples could be found in the following class SubprocessMockExample.

Release Notes

see https://camunda.github.io/camunda-bpm-mockito/release-notes/

Limitations

  • Though it is possible to use arbitrary beans as expressions (myBean.doSomething()), we solely focus on Listeners (notify()) and Delegates (execute()) here, since this is the only way to apply automatic behavior. If you need to mock custom beans, you still can use some of the tools to register the mock, but can not use the fluent mocking or auto mocking feature. Due to the nature of automatic mocking, this is immanent and will not change.
  • Currently, only expression-delegates (${myDelegate}) are supported (as you do use with CDI/Spring)) but no FQN class names. This might and probably will change with future versions, it just has to be implemented ...
  • while automocking, expressions are only parsed for listeners and delegates, not for process variables.

Resources

Maintainer

License