Giter Site home page Giter Site logo

holunda-io / camunda-bpm-jgiven Goto Github PK

View Code? Open in Web Editor NEW
11.0 8.0 1.0 626 KB

Camunda specific stages and scenarios for the BDD testing tool JGiven.

License: Apache License 2.0

Kotlin 100.00%
camunda jgiven camunda-bpm-jgiven scenario bdd-tests testing testing-framework

camunda-bpm-jgiven's Introduction

Camunda BPM JGiven

Camunda specific stages and scenarios for the BDD testing tool JGiven written in Kotlin.

stable Camunda 7.20 Development braches Maven Central

Project Stats

Motivation

Starting from 2012, we are preaching that processes are no units. Behavior-driven development (BDD) and the underlying testing methodology of scenario-based testing is a way more adequate and convenient for writing process (model) tests.

Our first attempts addressed testing frameworks Cucumber and JBehave. For JBehave we were able to release an official Camunda BPM extension, but it turned out that the main problem in using it, was error-prone writing of the test specifications in Gherkin (text files) and glue code them with Java.

This is, where JGiven comes on the scene, allowing to write both in Java or any other JVM language by providing a nice API and later generating reports which are human-readable.

Usage

Add the following dependency to your Maven pom:

<dependency>
  <groupId>io.holunda.testing</groupId>
  <artifactId>camunda-bpm-jgiven</artifactId>
  <version>1.20.0</version>
  <scope>test</scope>
</dependency>

Features

JGiven supports separation of the glue code (application driver) into so-called stages. Stages contain assert and action methods and may be subclassed. This library provides a base class ProcessStage for building your process testing stages. Here is how the test then looks like (written in Kotlin):

JUnit5

@Deployment(resources = [ApprovalProcessBean.RESOURCE])
internal class ApprovalProcessTest :
  ScenarioTest<ApprovalProcessActionStage, ApprovalProcessActionStage, ApprovalProcessThenStage>() {

    @RegisterExtension
    val extension = TestProcessEngine.DEFAULT

    @ScenarioState
    val camunda = extension.processEngine

    @Test
    fun`should automatically approve`() {

        val approvalRequestId = UUID.randomUUID().toString()

        GIVEN
            .process_is_deployed(ApprovalProcessBean.KEY)
            .AND
            .process_is_started_for_request(approvalRequestId)
            .AND
            .approval_strategy_can_be_applied(Expressions.ApprovalStrategy.AUTOMATIC)
            .AND
            .automatic_approval_returns(Expressions.ApprovalDecision.APPROVE)

        WHEN
            .process_continues()

        THEN
            .process_is_finished()
            .AND
            .process_has_passed(Elements.SERVICE_AUTO_APPROVE, Elements.END_APPROVED)

    }
}

Here is the corresponding stage, providing the steps used in the test:

class ApprovalProcessActionStage : ProcessStage<ApprovalProcessActionStage, ApprovalProcessBean>() {

  @BeforeStage
  fun `automock all delegates`() {
    CamundaMockito.registerJavaDelegateMock(DETERMINE_APPROVAL_STRATEGY)
    CamundaMockito.registerJavaDelegateMock(AUTOMATICALLY_APPROVE_REQUEST)
    CamundaMockito.registerJavaDelegateMock(ApprovalProcessBean.Expressions.LOAD_APPROVAL_REQUEST)
  }

  fun process_is_started_for_request(approvalRequestId: String) = step {
    processInstanceSupplier = ApprovalProcessBean(camunda.processEngine)
    processInstanceSupplier.start(approvalRequestId)
    assertThat(processInstanceSupplier.processInstance).isNotNull
    assertThat(processInstanceSupplier.processInstance).isStarted
  }

  fun approval_strategy_can_be_applied(approvalStrategy: String) = step {
    getJavaDelegateMock(DETERMINE_APPROVAL_STRATEGY).onExecutionSetVariables(Variables.putValue(APPROVAL_STRATEGY, approvalStrategy))
  }

  fun automatic_approval_returns(approvalDecision: String) = step {
    getJavaDelegateMock(AUTOMATICALLY_APPROVE_REQUEST).onExecutionSetVariables(Variables.putValue(APPROVAL_DECISION, approvalDecision))
  }
}

JUnit4

@Deployment(resources = [ApprovalProcessBean.RESOURCE])
open class ApprovalProcessTest : ScenarioTest<ApprovalProcessActionStage, ApprovalProcessActionStage, ApprovalProcessThenStage>() {

    @get: Rule 
    val rule: ProcessEngineRule = StandaloneInMemoryTestConfiguration().rule()

    @ScenarioState
    val camunda = rule.processEngine

    @Test
    fun`should automatically approve`() {

        val approvalRequestId = UUID.randomUUID().toString()

        GIVEN
            .process_is_deployed(ApprovalProcessBean.KEY)
            .AND
            .process_is_started_for_request(approvalRequestId)
            .AND
            .approval_strategy_can_be_applied(Expressions.ApprovalStrategy.AUTOMATIC)
            .AND
            .automatic_approval_returns(Expressions.ApprovalDecision.APPROVE)

        WHEN
            .process_continues()

        THEN
            .process_is_finished()
            .AND
            .process_has_passed(Elements.SERVICE_AUTO_APPROVE, Elements.END_APPROVED)

    }
}

If you want to collect process test coverage during the test run, make sure to replace your rule declaration by the following:

  companion object {
  @get: ClassRule
  @JvmStatic
  val processEngineRule: ProcessEngineRule = TestCoverageProcessEngineRuleBuilder.create().build()
}

@get:Rule
val rule: ProcessEngineRule = processEngineRule

and add the following content into your camunda.cfg.xml:

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans   http://www.springframework.org/schema/beans/spring-beans.xsd">

  <bean id="processEngineConfiguration"
        class="org.camunda.community.process_test_coverage.engine.platform7.ProcessCoverageInMemProcessEngineConfiguration">
    <property name="history" value="full"/>
  </bean>
</beans>

The resulting report:

JGiven Process Report

Interested? Check out the examples.

License

This library is developed under

Apache 2.0 License

Contribution

We use gitflow for development. If you want to contribute, start and create an issue. Then fork the repository, create a feature branch and provide a pull-request against develop.

If you have permissions to release, make sure all branches are fetched and run:

 ./mvnw gitflow:release-start 
 ./mvnw gitflow:release-finish

from cli. This will update the poms of develop and master branches and start GitHub actions producing a new release.

camunda-bpm-jgiven's People

Contributors

a-hegerath avatar dependabot-preview[bot] avatar dependabot[bot] avatar jangalinski avatar mmiikkkkaa avatar p-wunderlich avatar rohwerj avatar srsp avatar stefanzilske avatar zambrovski avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Forkers

stefanzilske

camunda-bpm-jgiven's Issues

Unify handling of multiple parameters in process stage

Within the process stage class some methods take parameters as varargs and some as array, which leads to different handling while usage.

The methods task_is_visible_to_users, task_is_visible_to_groups and variables_are_not_present should also use varargs instead of array, to unify the handling of all methods.

Streamline continueIfAsync

continueIfAsync is a bit strange, as it suggests, that it will only continue, if the task is async(-after). But if it is not, it will throw an exception, which is fine, as the tests, where it is used, demanded an async continuation.

Proposed change: rename the parameter to isAsyncAfter to make the purpose clearer.

In addition, there should be a different method signature, without the parameter, which defaults is to false so it can be used from Java in a more efficient way.

AssertJ version is different from what camunda-bpm-assert uses

camunda-bpm-assert 8.0.0 uses AssertJ 3.16.1 but camunda-bom-jgiven uses AssertJ 3.13.2.

This leads to compatibility issues when used together in application that uses AssertJ 3.16.1 (and higher):

'org.assertj.core.api.AbstractAssert org.camunda.bpm.engine.test.assertions.bpmn.ProcessInstanceAssert.as(java.lang.String, java.lang.Object[])'
java.lang.NoSuchMethodError: 'org.assertj.core.api.AbstractAssert org.camunda.bpm.engine.test.assertions.bpmn.ProcessInstanceAssert.as(java.lang.String, java.lang.Object[])'
	at io.holunda.camunda.bpm.extension.jgiven.ProcessStage.task_is_completed_with_variables(ProcessStage.kt:182)

Proposed fix: Keep AssertJ version in sync with camunda-bpm-assert

provide "process_is_not_waiting_at"

Sometimes I want to verify an activity is not active. for example a user task in a loop is completed and not yet reactivated.

"has_passed" and "waits_in" won't do the job here

Step "external_task_is_completed" should accept lambda with actual execution

So far we assume, that the step "external_task_is_completed" has no side effects and only has to set/modify variables.

I now have a test case, where it is important that the actual worker method is executed, because it has side effects (message correlation, task completion) that have impact on the process state.

suggestion:

allow passing a lambda (LockedExternalTask) -> Unit to the step function, so I can reference my custom implementation or take full control over worker execution.

Support JUnit5

camunda-bpm-data has a mandatory dependency to ProcessEngineRule, which works with JUnit4 only.

camunda-bpm-junit5 allows Camunda tests with JUnit5, using a ProcessEngineExtension, so camunda-bpm-jgiven should not depend on ProcessEngineRule.

Support waiting in event subscription and wait and completion of multiple jobs

As a test developer I want to assert, that the process is waiting in (more than one) activities.
If this is true, I want to be able to execute multiple jobs providing the activity names.
This is helpful, if dealing with multiple "async after" nodes.

In addition, if the process is waiting in an event subscription, I want to be able to query this.

fix transitive dependencies

We ran into a problem today where a jgiven process test did not work and we couldn't easily find out why. Turned out we where not using the junit5/DualScenarioTest (v 1.2.2) as we thought, but junit/DualScenarioTest (v 1.2.0) ... which comes via this extension lib (v 0.2.0).

jgiven does a good job to separate modules so I can pick what I need (junit5 + spring has no deps on junit4). But when I use camunda-bpm-jgiven, I get everything that jgiven provides ... html report, spring, both junits ....

Instead we should either:

only keep the minimal (jgiven-core) dependencies we need to write the stages and leave wiring to the user

provide separate libs for junit4, junit5, ....

I'd prefer Option 1. Especially since the pom contains the hint: <!-- This is a test library not a BOM, trying to avoid transitive dependencies -->.

use camunda rule in BeforeScenario

When the Test extends ScenarioTest and defines a camunda ProcessEngineRule
then in the @BeforeScenario method of a stage
the process engine rule is null.

This does not allow us to register deployments in the beforeScenario lifecycle method.

Desired behaviour: camunda rule is initialized.

Possible Solution JGiven/Camunda RuleChain.

Handle multiple process_waits_in checks identically to a single check

The process_waits_in method exists in two overloaded variantes: with a single activityId and with multiple activityIds.

The current implementation of the multiple activityIds variant causes problems due to not finding the "job", while the single activityId variant works. The multiple activityId variant might use the same operation as the single activityId variant, this would eliminiate the problems.

BTW: Since the multiple activityIds uses varargs it automatically covers the single activityId variant. Therefore the single variant can be removed completely.

support multiple paths of execution

This is not a bug or a concrete feature request but rather a topic for discussion. Currently, the ProcessStage seems to not yet support multiple paths of execution. I am facing the situation where I want to write process tests for a process model where there is multiplicity in different ways:

  • by a parallel gateway
  • by a multi-instance subprocess
  • by multi-instance service tasks

I am wondering if and how camunda-bpm-jgiven could be be extended to ease testing in such scenarios.

  • The process_waits_in method could for example take multiple activity ids and assert that the process waits at each one of them.
  • The task_xxx methods could be overloaded to take another parameter, the task definition key, to be able to assert specifically for the different coexisting tasks (in their current implementation, these assertions rely on that a single tasks exists, otherwise they fail). But this does not address the issue of multiple instances of the same task definition, any ideas how they can best be distinguished for assertion?

Any thoughs and suggestions are welcome.

Bump versions

Support latest versions of Kotlin, Camunda and libs.

support scenario-style testing

Use Case:
I have a process that is basically a straight-through process, each task is async but there are no real wait-states like intermediate-events, timers or user-task.
So the majority of my jgiven tests reads:

.job_is_executed("foo")
.and()
.job_is_executed("bar")
...

each service tasks just does a simple data retrieval based on data I mocked beforehand.

Idea:

As an alternative to the imperative testing style we use in camunda-bpm-jgiven todaym where we manually move the token through the process to evaluate each step, we should support a scenario based approach.

I want to write

given().assumeOnWaitstate("foo","Lookup im Backend, liefert variable", stage -> stage.job_is_executed("foo"));

when().processRunsTilEnd(...)

then().has_passed("startEvent,"foo","endEvent")

Implementation

  • loop until processInstance is ended (Attention: isEnded has to be reloaded from the runtimeService, do not use the attribute of the processInstanceSupplier)
  • provide a Map<ActivityId,Consumer<Stage>> where we can register custom behaviour to be executed in the same way as we would in a step-function

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.