Coder Social home page Coder Social logo

camunda-community-hub / camunda-platform-7-mockito Goto Github PK

View Code? Open in Web Editor NEW
45.0 22.0 20.0 1.03 MB

Provides mock helpers to register delegate/listener mocks while testing processes

License: Apache License 2.0

Java 100.00%
camunda mockito testing java mock camunda-7-20

camunda-platform-7-mockito's Introduction

camunda-platform-7-mockito

Camunda 7.20

Maven Central codecov Apache License V.2

Important
With camunda 8 released, we renamed this extension to clarify that it is only supposed to work with the Camunda BPM 7 platform, beginning with 6.17.x the maven coordinates changed! This will also require that you change your imports to org.camunda.community.mockito. We are sorry for the inconvenience.

Table of Contents

simplify process mocking and testing

camunda-platform-7-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-platform-7-mockito in the test scope of your project:

<dependency>
  <groupId>org.camunda.community.mockito</groupId>
  <artifactId>camunda-platform-7-mockito</artifactId>
  <scope>test</scope>
  <version>7.20.0</version>
</dependency>

gradle (kts):

   testImplementation("org.camunda.community.mockito:camunda-platform-7-mockito:7.20.0")

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<>).when() construct, implement your own answer and pick up the parameter from the invocation context. JavaDelegate and ExecutionListener are providing their basic functionality 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));
  }
}

Possible Actions

  • Set single variable

You can set a single variable on execution with:

DelegateExpressions.registerJavaDelegateMock(BEAN_NAME/BEAN_CLASS)
  .onExecutionSetVariable("key", "value");
  • Set multiple varibales

You can set multiple variables on execution with:

DelegateExpressions.registerJavaDelegateMock(BEAN_NAME/BEAN_CLASS)
  .onExecutionSetVariables(createVariables()
    .putValue("foo", "bar")
    .putValue("foo2", "bar2")
  );
  • Throw bpmn error

You can throw an error on execution with:

DelegateExpressions.registerJavaDelegateMock(BEAN_NAME/BEAN_CLASS)
  .onExecutionThrowBpmnError("code", MESSAGE);
  • Consecutive set variables for multiple delegate executions

You can set different variables on consecutive executions with:

DelegateExpressions.registerJavaDelegateMock(BEAN_NAME/BEAN_CLASS)
  .onExecutionSetVariables(Map.of("foo", "bar"), Map.of("bar", "foo"));
  1. invocation of the delegate the variable "foo" with the value "bar" is set
  2. invocation of the delegate the variable "bar" with the value "foo" is set

Easy register and verify mocks

In addition to 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 getJavaDelegateMock for delegates. And getExecutionListenerMock / getTaskListenerMock for listeners.

@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 time, you just want to confirm that a certain variable was set (or a dueDate, a candidate, ...).

In these cases, use the Delegate fakes. They 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.registerCallActivityMock() you can easily register a mocked process which is able to act with the following 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
  • onExecutionThrowEscalation ... the MockProcess will throw escalation with the given code at the end event

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 CallActivityMockExampleTest.

Mocking of message correlation builder

Sometimes you have services or delegates responsible for the execution of message correlation with your process engine. Camunda provides a fluent builder API for creation a message correlation and running it.

class MyCorrelator {

  private final RuntimeService runtimeService;
  private final String value;
  private final String businessKey;

  MyCorrelator(RuntimeService runtimeService, String businessKey, String value) {
    this.runtimeService = runtimeService;
    this.value = value;
    this.businessKey = businessKey;
  }

  void correlate() {
    this.runtimeService
      .createMessageCorrelation("MESSAGE_NAME")
      .processDefinitionId("some_process_id")
      .processInstanceBusinessKey(businessKey)
      .setVariable("myVar1", value)
      .correlate();
  }
}
 

In order to test those, you can use the following helper:

package org.camunda.community.mockito;

import org.camunda.bpm.engine.RuntimeService;
import org.camunda.bpm.engine.runtime.MessageCorrelationBuilder;
import org.junit.Test;

import static org.mockito.Mockito.*;

public class MessageCorrelationMockExample {


  @Test
  public void mock_messageCorrelation() {

    // setup mock
    final RuntimeService runtimeService = mock(RuntimeService.class);
    final MessageCorrelationBuilder correlation = ProcessExpressions.mockMessageCorrelation(runtimeService, "MESSAGE_NAME");
    final MyCorrelator serviceUnderTest = new MyCorrelator(runtimeService, "my-business-key", "value-1");

    // execute correlation, e.g. in a class under test (service, delegate, whatever)
    serviceUnderTest.correlate();

    // verify
    verify(correlation).correlate();
    verify(correlation).processDefinitionId("some_process_id");
    verify(correlation).processInstanceBusinessKey("my-business-key");
    verify(correlation).setVariable("myVar1", "value-1");

    verify(runtimeService).createMessageCorrelation("MESSAGE_NAME");

    verifyNoMoreInteractions(correlation);
    verifyNoMoreInteractions(runtimeService);
  }
}

Stubbing and verifying access to Camunda Java API services to access process variables

If you use camunda-bpm-data library to access process variables, you might want to test that access. If you are testing DelegateTask or DelegateExecution code, the examples above already gives you possibilities to do so. If your code relies on direct access to Camunda Java API services (RuntimeService, TaskService and CaseService) you might need to stub them and verify with the help of ServiceExpressions helper:

package org.camunda.community.mockito;

import io.holunda.camunda.bpm.data.factory.VariableFactory;
import org.camunda.bpm.engine.RuntimeService;
import org.camunda.community.mockito.ServiceExpressions;
import org.junit.Test;

import java.util.UUID;

import static io.holunda.camunda.bpm.data.CamundaBpmData.booleanVariable;
import static io.holunda.camunda.bpm.data.CamundaBpmData.stringVariable;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;

public class RuntimeServiceAwareServiceTest {

  private static final VariableFactory<String> ORDER_ID = stringVariable("orderId");
  private static final VariableFactory<Boolean> ORDER_FLAG = booleanVariable("orderFlag");

  @Test
  public void check_stubbed_access() {

    // setup mock
    final RuntimeService runtimeService = mock(RuntimeService.class);
    // stub access
    ServiceExpressions.runtimeServiceVariableStubBuilder(runtimeService)
                      .defineAndInitializeLocal(ORDER_ID, "initial-Value")
                      .define(ORDER_FLAG)
                      .build();
    // setup service
    final RuntimeServiceAwareService serviceUnderTest = new RuntimeServiceAwareService(runtimeService);
    // setup verifier
    final RuntimeServiceVerification verifier = ServiceExpressions.runtimeServiceVerification(runtimeService);

    String executionId = UUID.randomUUID().toString();

    // execute service calls and check results
    serviceUnderTest.writeLocalId(executionId, "4712");
    String orderId = serviceUnderTest.readLocalId(executionId);
    assertThat(orderId).isEqualTo("4712");

    assertThat(serviceUnderTest.flagExists(executionId)).isFalse();
    serviceUnderTest.writeFlag(executionId, true);
    assertThat(serviceUnderTest.flagExists(executionId)).isTrue();
    Boolean orderFlag = serviceUnderTest.readFlag(executionId);
    assertThat(orderFlag).isEqualTo(true);

    // verify service access
    verifier.verifySetLocal(ORDER_ID, "4712", executionId );
    verifier.verifyGetLocal(ORDER_ID, executionId);
    verifier.verifyGetVariables(executionId, times(2));
    verifier.verifySet(ORDER_FLAG, true, executionId);
    verifier.verifyGet(ORDER_FLAG, executionId);
    verifier.verifyNoMoreInteractions();

  }
}

Release Notes

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

Release Process

  • All issues should be added to a milestone
  • close the milestone to generate a github-release draft, containing all changes
  • publish the github-release (mark as latest release)
  • watch the actions/release pipeline
  • file an issue mentioning camundacommunity/devrel and mark issue with `waitingforcamunda``
  • fingers crossed

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 other 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

camunda-platform-7-mockito's People

Contributors

actions-user avatar berndruecker avatar chaima-mnsr avatar chrkarow avatar davidellermannikor avatar dependabot[bot] avatar fml2 avatar ingorichtsmeier avatar jangalinski avatar lwille avatar pschalk avatar puzan avatar renovate[bot] avatar roman-mkh avatar sebeichholz avatar snnair avatar srsp avatar tj-developer avatar zambrovski avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

camunda-platform-7-mockito's Issues

NullPointerException on calling asc() or desc() on mocked Queries

I have the following Code in my Service-Layer (within the Method taskList()):

taskService
  .active()
  .initializeFormKeys()
  .taskUnassigned()
  .taskCandidateUser(authentication.getName())
  .orderByTaskPriority().asc();

Running a Unit-Test with BPM-Mockito like this

QueryMocks.mockTaskQuery(taskService).list(someList);
testService.taskList();

throws a NullPointerException on the Method asc().

Am I doing something wrong or is there a failure within the mocked TaskService?

release 3.1.0

release version: 3.1.0
next dev-version: 3.2.0-SNAPSHOT

Hi Christian,

I fixed a very nasty issue with DelegateTaskFake and candidateGroups/Users today after running into an UnsupportedOprationException ... and though this would have justifed a new release, I also finally got camundas typedValue implementaion and adopted it for the delegate mocks.

regards
Jan

Use one central class CamundaMockito

separation of "expressions" and "delegateExpressions" proved to be very confusing in project situations. Idea: provide one class that combines the power of both.

release 2.1

next dev version 2.2-SNAPSHOT

already found a bug ...

Provide a fluent mock for Runtime service

If my code does something like this:

String myVariable = runtimeService.getVariable("Foo");

I want to be able to mock this behavior in test and provide a value for myVariable.

remove needle dependency

needle is not needed by this module, if you want to use it, include it yourself (or use camunda-bpm-needle)

conflict with different guava versions in test framework and prod code

We use guava 19 in the framework. No big deal, since it is a test dependency only. But it is "provided" and conflicts if the prod-application under test uses a different guava version.

options:

  • transitive dep to latest guava
  • remove guava dep completely
  • stick to "legacy" guava that does not hurt anyone (13 is supported by Jboss 7 as default)

FluentMocks: support onExecutionThrowException

for testing of errorhandling/fallback it will be useful to throw exceptions instead of raising bpmn errors.
This should be supported by the mocking api so the user is no longer forced to implement anonymous delegates/listerners.

Provide possibility to register subprocess mocks

Requirement: When Testing a process with a CallActivity, it should be possible to perform the test without having the original sub-process deployed. A dummy process should be used instead. The only thing that matters for this sub-process are the mapped return variables, so the main process can continue.

Acceptance criteria/MVP:

  • Provide possibility to register subprocess mocks and define its output.
  • It should also be possible to verify if the subprocess was executed.
  • the sub-process has to be cleaned up after the test ran (managed by the ProcessEngineRule)

Delegate Fakes should support model element


    private final FlowElement flowElement = Mockito.mock(FlowElement.class, RETURNS_DEEP_STUBS);

  public DelegateExecutionFake withType(String type) {
        when(flowElement.getElementType().getTypeName()).thenReturn(type);
        return this;
    }

    @Override
    public FlowElement getBpmnModelElementInstance() {
        return flowElement;
    }

release 2.2

I fixed a somehow critical bug #26 discovered by @mufasa1976 yesterday which is worth a fix-release.

Release Version: 2.2
New Dev Version: 2.3-SNAPSHOT

Note: Seems I forgot who is the sponsor for this extension ... @hawky-4s- could you take over or delegate (and add the camunda sponsor to the maintainers list in the README)? Thanks!

juelNameFor existing mockito mock does not work

Consider a mock MyJavaBean bean = mock(MyJavaBean.class) where MyJavaBean is annotated with Named or Component/Service.

Expected juelName is "myJavaBean", but instead its "myJavaBeanEnhancedMockito...." because reflection can not get the annotation on the mocked instance.

use latest (3.11) assertj version

This affects the camunda-bpm-assert extension but also our tests since CallActivityExampleTest failed when I changed from 3.5.2 to 3.9.0

Add support for camunda 7.4 (7.5)

The new services inroduced in 7.4 are not mockable so far .. when working on this just switch to 7.5 as the api remained the same.

remove milestone 1.1

next release will be 2.0 since there will be some dependency changes that might break or required updating projects using this module

Support mockito 2.x

I'm using mockito 2.15.0 and receive the following error using the CamundaMockito.mockTaskQuery()
method:

java.lang.ClassCastException: org.camunda.bpm.engine.task.TaskQuery$MockitoMock$1632608987 cannot be cast to org.camunda.bpm.engine.task.Task

	at org.camunda.bpm.engine.task.TaskQuery$MockitoMock$1632608987.singleResult(Unknown Source)
	at org.camunda.bpm.engine.task.TaskQuery$MockitoMock$1632608987.singleResult(Unknown Source)
	at org.camunda.bpm.extension.mockito.query.AbstractQueryMock.singleResult(AbstractQueryMock.java:83)
	at org.camunda.bpm.extension.mockito.query.AbstractQueryMock.<init>(AbstractQueryMock.java:66)
	at org.camunda.bpm.extension.mockito.query.TaskQueryMock.<init>(TaskQueryMock.java:12)
	at org.camunda.bpm.extension.mockito.QueryMocks.mockTaskQuery(QueryMocks.java:61)
	at org.camunda.bpm.extension.mockito.CamundaMockito.mockTaskQuery(CamundaMockito.java:464)
	at foo.QueryMockSpike.test(QueryMockSpike.java:46)

Here is the source code to reproduce:

package foo;

import org.camunda.bpm.engine.TaskService;
import org.camunda.bpm.engine.task.Task;
import org.camunda.bpm.engine.task.TaskQuery;
import org.camunda.bpm.extension.mockito.CamundaMockito;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.verify;

public class QueryMockSpike {

    public static class SomeServiceUsingQuery {

        private final TaskService taskService;

        public SomeServiceUsingQuery(TaskService taskService) {
            this.taskService = taskService;
        }

        public Task findTaskByKey(String key) {
            return taskService.createTaskQuery().taskDefinitionKey(key).singleResult();
        }
    }

    @Rule
    public final MockitoRule mockito = MockitoJUnit.rule();

    @Mock
    private TaskService taskService;

    @InjectMocks
    private SomeServiceUsingQuery service;

    @Mock
    private Task task;

    @Test
    public void test() {
        TaskQuery queryMock = CamundaMockito.mockTaskQuery(taskService).singleResult(task);

        assertThat(service.findTaskByKey("foo")).isEqualTo(task);
        verify(queryMock).taskDefinitionKey("foo");
    }
}

unable to mock listPage

The Query-Interface offers also the Method

  List<U> listPage(int firstResult, int maxResults);

but this can't be mocked with QueryMocks.

Provide possibility to register embedded subprocess mocks

Requirement: When Testing a process with an embedded subprocess, it should be possible to perform the test without defining delegates of the subprocess. The subprocess should get mocked and just return an pre defined return value.

Acceptance criteria/MVP:

  • Provide possibility to register embedded subprocess mocks and define its output.
  • It should also be possible to verify if the embedded subprocess was executed.
  • the sub-process has to be cleaned up after the test ran (managed by the ProcessEngineRule)

Additional behaviours:

  • Process may be started synchroniously/asynchroniously
  • Run into an Error
  • Send a message
  • Wait for a message receive
  • Wait for a timer

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.