Unit Testing using Spock Framework for Cucumber Style Testing + Shared Engine Unit Testing: DMN+BPMN

For anyone that would like to do unit testing on your BPMN processes that are used in Shared Engine environments, and you are not deploying projects with the testings, I have put together the starting structure of IMO a simpler unit testing process that more business focused use cases can handle

I used Spock Framework, so you can write in groovy:

something like this:

import org.camunda.bpm.engine.runtime.ProcessInstance;
import org.camunda.bpm.engine.test.Deployment;
import org.camunda.bpm.engine.test.ProcessEngineRule;
import static org.camunda.bpm.engine.test.assertions.ProcessEngineTests.*;

import org.junit.Rule;

import spock.lang.*

@Narrative("""
As a user of Camunda
I want to run a basic test of the engine
to see if the engine will function with Spock Testing
""")
@See("http://forum.camunda.org") // Can also be applied to methods. Can be a array.
@Issue("http://my.issues.org/FOO-1") // Can also be applied to methods. Can be a array.
@Title("Camunda Test Process Example")

class CamundaHelloWorld1Spec extends Specification {

  @Rule
  public ProcessEngineRule processEngineRule = new ProcessEngineRule('camunda_config/camunda.cfg.xml');

  @Deployment(resources = ["bpmn/testProcess.bpmn"])
  def "Test testProcess.bpmn"() {
    when:_ "Starting Process Instance"
      def processInstance = runtimeService().startProcessInstanceByKey("testProcess")
      println processInstance.getProcessDefinitionId()

    then:_ "Process is active"
      assertThat(processInstance).isActive()

    and:_ "only 1 instance is running"
      assertThat(processInstanceQuery().count()).isEqualTo(1)

    and:_ "there is a active task"
      assertThat(task(processInstance)).isNotNull()

    and:_ "We complete the task"
      complete(task(processInstance))

    and:_ "The process has ended"
      assertThat(processInstance).isEnded()
  }
}

Work has started here: https://github.com/DigitalState/Camunda-Spock-Testing/tree/master/BasicExample

Its a fully working example. You can run multiple tests by removing the @Ignore annotation in the CamundaHelloWorld2Spec class.

See the readme for known issues: Unsure if its just my configurations or a current limit of the @Deployment and @Rule that camunda implements for testing, but basically you cannot use the @Stepwise (chain test steps together` because the @Deployment annotation does not seem to want to execute at the class level or when used at the Spock’s setupSpec() method. Would be great for anyone who uses testing extensively to take a look, as its beyond my knowledge. @thorben @camunda This has been resolved. See: https://github.com/DigitalState/Camunda-Spock-Testing/blob/master/BasicExample/src/test/groovy/8_Stepwise/StepwiseSpec.groovy

My plan is to add this to my other project: https://github.com/DigitalState/ProcessProjectTemplate so you can deploy to the engine through REST and also run unit tests pre-deployment.

If you have other use cases, would be interested to hear them!

And if you have better configuration ideas / better way to setup the example please share!


Note about cucumber: I explored using cucumber extensively, but it does not provide support for @Rule, and the JUnit 3 style of TestCase I could not get to work. Also the documentation is HORRIBLE for JVM, Groovy, and nashorn… So Spock seemed like a nice middle ground

Cross Reference to what looks like a bug:

Current workaround would be to use a env indicator variable in the process and have the resource location of a script change from deployment://file.js to deployment://bpmn/file.js (where this is the folder path (src/test/resources/…)

EDIT: This bug is caused by the deployment annotation supporting classpath resources rather than string resources / classpath resources with names. See the link above for fix.

Made a update to test examples:

  1. JSON SPIN Testing: Compare SPIN JSON created by process with SPIN JSON in test
  2. folder structure cleanup for easier business user understanding
  3. Hamcrest pattern matching examples
  4. Variable Existing with Type Checks

Add Wiremock examples:

So based on https://groups.google.com/forum/#!topic/camunda-bpm-users/p3KZqdjHqQ4 wiremock or intercept was interesting examples.

Intercept seemed complicated, and seems to have multiple layers of abstraction that people need to understand :-1:

So https://github.com/DigitalState/Camunda-Spock-Testing/tree/master/BasicExample/src/test/groovy/5_Wiremock was put together

import org.camunda.bpm.engine.runtime.ProcessInstance
import org.camunda.bpm.engine.test.Deployment
import org.camunda.bpm.engine.test.ProcessEngineRule
import static org.camunda.bpm.engine.test.assertions.ProcessEngineTests.*

import org.junit.Rule

import spock.lang.*

import com.github.tomakehurst.wiremock.junit.WireMockRule
import com.github.tomjankes.wiremock.WireMockGroovy


@Narrative("""
Using Wiremock as basic usage
""")
@Title("Wiremock - Basic Usage")

class WiremockSpec extends Specification {

  def wmPort = 8081

  @Rule
  public ProcessEngineRule processEngineRule = new ProcessEngineRule('camunda_config/camunda.cfg.xml');
  
  @Rule
  WireMockRule wireMockRule = new WireMockRule(wmPort)
  
  def wireMockGroovy = new WireMockGroovy(wmPort)

  @Deployment(resources = ["bpmn/5_Wiremock/wiremock.bpmn"])
  def "WireMock Test"() {
    given: _ "Web Server is running"
      wireMockGroovy.stub {
          request {
              method "GET"
              url "/some/thing"
          }
          response {
              status 200
              body "Some body"
              headers {
                  "Content-Type" "text/plain"
              }
          }
      }

    when:_ "Starting Process Instance"
      def startingVars = [
        '_env': 'unit-test'
      ]
      def processInstance = runtimeService().startProcessInstanceByKey("wiremock", startingVars)

    then:_ "Process has the Wiremock Response and _env variable"
      def processInstanceId = processInstance.getProcessInstanceId()
      def historicVariableInstanceQuery = historyService().createHistoricVariableInstanceQuery()
                                                          .processInstanceId(processInstanceId)

      def varValues = historicVariableInstanceQuery.list().collectEntries({[(it.getName().toString()) : it.getValue()]})
      println varValues.sort().toString()
      def desiredValue = ['ws_response':'Some body', '_env':'unit-test']
      
      assertThat(processInstance).hasVariables('ws_response')
      assert varValues.sort().toString() == desiredValue.sort().toString()
  }
}

and to get around the issue of the wiremock URL, we use something like the following as a script for the URL of the HTTP call. In this case its using HTTP-Connector:

function buildUrl(env)
{
  var path = '/some/thing'
  
  switch (env) {
    case 'unit-test':
      return 'http://localhost:8081' + path

    case 'test':
      return 'http://some.test.env.url' + path

    default:
      throw 'Cannot build URL - No valid env was given'
  }
}

var env = execution.getVariable('_env')
buildUrl(env)

In practice i would be pulling the base URL from something like a config file (https://github.com/StephenOTT/ProcessProjectTemplate/blob/master/docs/helpers.md#load-configjson-into-memory-and-into-a-process-variableoptional). IMO this is much more realistic for Multi-Env(Test, Dev, Stage, Prod), etc.

We use wiremock + https://github.com/tomjankes/wiremock-groovy for the groovy bindings that let us easily use it in Spock Framework :tada:

As soon as the deployment:// bug listed above is sorted, using wiremock in this pattern + the JSON SPIN validation, should give all of the patterns needed for Shared Engine / Rest API deployment testing. Scripts can be individually unit tested with mocks and loading the specific engine within Spock, the web service calls can be fully tested using wiremock, and variables can be bulk evaluated with the HashMap examples :+1:

I have added hamcrest pattern testing: http://hamcrest.org/JavaHamcrest/


And Finally the last significant part of the puzzle ;):

Nashorn Script Unit Testing:

The Groovy script:

// import org.camunda.bpm.engine.runtime.ProcessInstance
// import org.camunda.bpm.engine.test.Deployment
// import org.camunda.bpm.engine.test.ProcessEngineRule

//brings in Camunda BPM Assertion + AssertJ core.api.Assertions
import static org.camunda.bpm.engine.test.assertions.ProcessEngineTests.*
// http://joel-costigliola.github.io/assertj/core/api/index.html
// http://camunda.github.io/camunda-bpm-assert/apidocs/org/camunda/bpm/engine/test/assertions/ProcessEngineTests.html
// http://joel-costigliola.github.io/assertj/

// Used for the Spock Mock of the 'execution' variable in the script
import org.camunda.bpm.engine.delegate.DelegateExecution

// import org.junit.Rule

import spock.lang.*

// import static org.camunda.spin.Spin.*


import javax.script.ScriptEngine
import javax.script.ScriptEngineManager


@Narrative("""
As a bpmn developer I want to Unit Test Nashorn Scripts
""")
// @See("http://forum.camunda.org") // Can also be applied to methods. Can be a array.
// @Issue("http://my.issues.org/FOO-1") // Can also be applied to methods. Can be a array.
@Title("Nashorn Unit Testing Test")

class NashornSpec extends Specification {

  // Use when you want to run the Camunda Engine
  // @Rule
  // public ProcessEngineRule processEngineRule = new ProcessEngineRule('camunda_config/camunda.cfg.xml');

  @Shared ScriptEngine engine = new ScriptEngineManager().getEngineByName('nashorn');

  // Use when you want to deploy a resource with the Camunda engine
  // @Deployment(resources = ['bpmn/7_Nashorn/nashorn.bpmn', 'bpmn/7_Nashron/Transforms.js'])
  def 'Nashorn Unit Testing'() {
    setup:_ 'Setup Mocks and binding'
      // Generates a Spock Mock using the DelegateExecution interface from Camunda engine
      def execution = Mock(DelegateExecution)
      
      // When a script uses .getVariable('dog') then return the value 'Frank'
      // See: http://spockframework.org/spock/docs/1.0/interaction_based_testing.html#_stubbing
      execution.getVariable('dog') >> 'Frank'

      // Create a bindning for the 'execution' Mock() and the Nashorn engine.
      // The binding allows the Nashorn engine to execute code from the Spock Test / the Spock Mock.
      engine.put('execution', execution)

    when:_ 'Execute overall .js script and call "transform" function'
      // Gets the specific .js script as text/a string.
      // The path being src/test/resources/bpmn/7_Nashron/Transforms.js
      // src/test/resources is defaulted 
      def source = this.class.getResource('bpmn/7_Nashron/Transforms.js').text

      // .eval executes the script, so anything that is not wrapped in a function will be called.
      // Given the way Camunda executes its scripts, this will usually mean that the full script will be executed.
      // evalResult is the returned value of the script. See the .js script for further comments.
      def evalResult = engine.eval(source);

      // Example of calling a specific function.
      Map result = engine.invokeFunction('transform', [name: [first: 'James', last: 'Bond']])

    then:_ 'Assert that Frank is returned by overall script, and that transform returns James Bond'

      // Use this line if you want to test the number of executions AND the outputted value
      // See: http://spockframework.org/spock/docs/1.0/interaction_based_testing.html#_combining_mocking_and_stubbing
      // 1 * execution.getVariable('dog') >> 'Franks'

      assertThat 'The js script returned the string "Frank"',
                  evalResult == 'Frank'

      assertThat 'The firstName is "James"',
                  result.firstName == 'James'

      assertThat 'The lastName is "Bond"',
                  result.lastName == 'Bond'

  }
}

And the .js script:

function transform(person) {
    return {
      firstName: person.name.first, 
      lastName: person.name.last
      }
}

var dogName = execution.getVariable('dog')
dogName   // Variable needs to be returned at the end or else groovy gets 'null'

You can un-comment the imports and specific camunda engine imports which allow you to run the engine + script unit tests. You could also control the order of the tests to ensure that the Engine test only runs after the individual scripts are successful.

Also note that as you use scripts in different contexts such as a user task, where ‘Delegate Execution’ is not used, you should be Mocking the relevant interfaces. See: https://docs.camunda.org/manual/7.8/user-guide/process-engine/scripting/#variables-available-during-script-execution for the variables and their specific classes you should be creating a Mock for.

Given the flexibility of the Spock Mock and Stubbing, i HIGHLY recommend that you read the entire page about Spock Interaction Testing: http://spockframework.org/spock/docs/1.0/interaction_based_testing.html. There are several very little things to know that are very significant and if you place them in the wrong spots or dont use the proper format for the proper scenario (like i did) you will waste a lot of time with weird results.

Enjoy!

1 Like

Full End to End Example added:

Tests BPMN file with external JS resources for script tasks and gateway logic.

  1. There is a Process Application Deployment + teardown/cleanup
  2. Datatable usable to quickly test multiple scenarios against the JS scripts and inner functions (https://github.com/DigitalState/Camunda-Spock-Testing/blob/master/End-to-End/src/test/groovy/end-to-end/EndToEndNashornSpec.groovy#L66)
  3. BPMN BPMAssert Testing
  4. Mocking of Camunda interfaces for method chaning
  5. Loading SPIN library in the nashorn engine
  6. Binding execution variable (same principal as binding the task variable for user task listeners)
  7. Datatable BPMN testing allowing you to test multiple BPMN scenarios with ease. (https://github.com/DigitalState/Camunda-Spock-Testing/blob/master/End-to-End/src/test/groovy/end-to-end/EndToEndSpec.groovy#L103).
  8. SPIN JSON comparison of what engine produces and what is expected.

If anyone has any ideas on what patterns are missing please let me know!!

I have added a example of doing DMN unit testing which leverages the Spock Data tables:

See the readme for further breakdown.

Inspiration was based on https://github.com/camunda/camunda-engine-dmn-unittest, but that example was not dynamic for each variation of the test, where is duplicated code or had to ‘roll your own’ so it could run multiple input/output combinations:


cc: @Philipp_Ossler @menski