How to test JavaDelegates without Dependency Injection?

Hello,

I’m relatively new to Camunda, so correct me if I’m missing out on something.

But I’m trying to test my process and especially my service tasks.
I know there are various frameworks and tutorials out there, like this and camunda assert, platform scenario, mockito, coverage, …

When it comes to JavaDelegates I understand the concept written (here), by using a mock instead of the real service.
Unfortunately I don’t use Beans in my project, so a lot of the proposed examples are not applicable to my case.

I want to do two things:

  1. Run my process as shown in camunda platform scenario without calling my JavaDelegates and only specify the answers.
  2. Run my process and with them the JavaDelegates and only mock service calls.

(I know there is a thing called when(myApplicationMock.waitsAtServiceTask(“ExampleServiceTask”)).thenReturn((task) → task.complete());, but this is only for External Tasks and I don’t have them (yet, I’m working on it))

I hope you can help me.

Regards

Hello @nikitaax3 ,

usually, you register a MockExpressionManager in the camunda.cfg.xml. Then, you can use Mocks.register(beanName, bean) to register the Delegate Beans without having an application context.

The snippet you refer to is from Scenario-Tests. The difference between a „classic“ unit test and a scenario test is that in the unit test, you move through the process instance step by step while in a scenario test, you define the handling of interaction points and let a scenario run that can then be asserted on.

The external task pattern is very nice for scenario testing! Very good catch :wink:

I hope this helps

Jonathan

Hello @jonathan.lukas,

thank you for your reply. This helps a little bit.

I’m currently trying out the normal mock of the service task, but it doesn’t work. Just to don’t miss anything, here is what I’ve done:
I work with JUnit5 and Camunda Version 7.17 and used therefore:

  • junit-jupiter-api
  • mockito-junit-jupiter
  • camunda-bpm-junit5
  • assertj-core

Added the MockExpressionManager in the camunda.cfg.xml file. In there I have also the StandaloneInMemProcessEngineConfiguration for the test deployment of camunda.

I have this annotation @ExtendWith(ProcessEngineExtension.class). Do I need this one @ExtendWith(MockitoExtension.class) as well? (tried it out, didn’t change anything)

This is my little test process:

I connected the Java Delegate via Java class.

Here is my implementation:

package com.process;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.camunda.bpm.engine.delegate.DelegateExecution;
import org.camunda.bpm.engine.delegate.JavaDelegate;

public class ExampleServiceImpl implements JavaDelegate {

    private static final Logger LOG = LogManager.getLogger(ExampleActivity.class);

    @Override
    public void execute(DelegateExecution execution) throws Exception {
        // Do something...

        LOG.info("Hello I'm here in the wonderful ExampleServiceImpl and do stuff!");
    }
}

I can run the process and everything works, even in the test case below.

@Test
    @Deployment(resources = { "test-process-files/test-diagram.bpmn" })
    public void shouldExecuteExampleActivityTest() {
        ProcessInstance processInstance = runtimeService().createProcessInstanceByKey("TestProcessID").execute();
        assertThat(processInstance).isStarted();
        assertThat(processInstance).isWaitingAt("ExampleActivityID");
        execute(job());
        assertThat(processInstance).hasPassed("ExampleActivityID");
        assertThat(processInstance).isWaitingAt("EndEvent");
        execute(job());
        assertThat(processInstance).isEnded();
    }

That’s great, but as I said, I have two use cases and want a) to mock the JavaDelegate Impl (and set some process vars) and b) mock only parts of the JavaDelegate Impl, like a call to another service.

I tried to mock the service task but I don’t think the MockExpressionManager is working in my environment, because it says the Mock is never touched.

Wanted but not invoked:
exampleActivityTask.execute(<any>);
-> at com.test.ProcessTestCase.shouldExecuteExampleActivityTest(ProcessTestCase.java:130)
Actually, there were zero interactions with this mock.

I created it like this:

@Mock
    private ExampleServiceImpl exampleActivityTask;

   @BeforeEach
    public void defineHappyScenario() {
        MockitoAnnotations.openMocks(this);
        Mocks.register("ExampleServiceImpl", exampleActivityTask);
    }

Did I miss anything? Did I do something wrong?

I hope you don’t get scared off by all this text :smiley:

Hello @nikitaax3 ,

how did you configure your BPMN diagram? The way I assumed it to be configured (and what I would recommend) is as delegateExpression which would look like this: ${ExampleServiceImpl}.

If it is registered as Java Class, the engine would use the Classloader to create a new instance whenever it is invoked.

This would disallow you to do assertions on it.

Maybe this helps

Jonathan

Hello @jonathan.lukas ,

nope, I registered it as a Java Class.

In order to use the Delegate Expression I have to use some sort of Dependency Injection, right?

What is Camunda’s Best Practice on testing Service Tasks that are registered as Java Classes?
Here under Forces on Testing is says that you could Mock inside the Java Delegate. Can you give me an example for that?

Thank you for your help so far. It isn’t great to find out that most options don’t work in my case but at least now I know why :slight_smile:

Hello @nikitaax3 ,

true, using Java Classes as Delegate is ok. But then, you will not need to register Mocks AND you will not be able to do assertions on them.

As stated, the focus is then to test the “real” delegate.

Do you have a constraint that will disable you from using delegate expressions?

If you now really want to use java classes AND do assertions on them, you will have to register an alternative “artifactFactory” (I am not sure whether there is something like a MockArtifactFactory) to the process engine configuration. From there, you could inspect the implementation of Mocks.register(…) and maybe reuse the used mapping.

I hope this helps

Jonathan

Hello @jonathan.lukas,

I don’t use any dependency injection in my project and also don’t want to, because reasons (don’t ask me why, idk, I’m just here for testing ;)).

For now I don’t know what your proposed “artifactFactory” means, but I’m going to look into that. Thank you.

That was one part of my use cases. Another thing I want to do is execute the Java Delegate and mock only parts of it, like a service call. Do you know how to do that?

To further explain what I’m trying to do, here some code:

public class ExampleServiceImpl implements JavaDelegate {

    SomeService myService;

    @Override
    public void execute(DelegateExecution execution) throws Exception {
        // Do something...

        LOG.info("Hello I'm here in the wonderful ExampleServiceImpl and do stuff!");

        result = myService.callExternalDatabase();
        // do something with the result
    }

}

In the example above I would like to mock myService to only test the logic before and after that. I think with dependency injection this would be easier, at least it looks like it in other examples.

Hello @nikitaax3 ,

this is a very quick draft of how this ArtifactFactory could look like:

import org.camunda.bpm.engine.ArtifactFactory;
import org.camunda.bpm.engine.test.mock.Mocks;

public class MockArtifactFactory implements ArtifactFactory {
  @Override
  public <T> T getArtifact(Class<T> clazz) {
    return Mocks.getMocks().values().stream()
        .filter(o -> clazz.isAssignableFrom(o.getClass()))
        .map(clazz::cast)
        .findFirst()
        .orElseThrow(() -> new RuntimeException("No mocked delegate present for class " + clazz));
  }
}

As mentioned above, you will need to register this as “artifactFactory” in the camunda.cfg.xml.

Then, you will use exactly the objects you registered via Mocks.register() before.

Jonathan

1 Like

Hello @jonathan.lukas,

thank you for your help. With the right words for searching I was even able to find the corresponding github and blogpost with further information. In fact, it was already in my list of links not to forget :neutral_face: bc they could be useful in the future :smiley:

Thank you for reminding me and the explanation!

Have a nice weekend :wave:

Beatrix

1 Like