DMN Tester

You can integrate the DMN Tester in your project pretty simple.

Why

The DMN Tester lets you easily validate your DMNs, that you create or get from the business analysts.

The DMN Tester gives you a UI, to configure a test for a DMN. As there is already some information in your domain model, we must only define the rest. And so we can directly run the tests, without configure them manually in the UI.

See Github for more information on what the DMN Tester is all about.

Get Started

The DMN Tester DSL use the DMNs you created - in this context I refer to the Bpmn DSL

Let's start with a basic example:

// put your dmns in the dmn package of your project (main)
package camundala.examples.invoice.dmn
// import the projects bpmns (DMNs)
import camundala.examples.invoice.bpmn.*

object ProjectDmnTester 
  extends CompanyDmnTester:
      
      startDmnTester()

      createDmnConfigs(
          InvoiceAssignApproverDMN
            .testValues(_.amount, 249, 250, 999, 1000, 1001),
            .dmnPath("invoiceBusinessDecisions")
          // for demonstration - created unit test - acceptMissingRules just for demo
          InvoiceAssignApproverDmnUnit
            .acceptMissingRules
            .testUnit
            .dmnPath("invoiceBusinessDecisions")
            .inTestMode
        )

end ProjectDmnTester

Run the DMN Tester

In your sbt-console:

dmn/run

This starts the Docker container and makes the whole process pretty nice and fast. The following steps are done:

createDmnConfigs

A DSL to create the DMN Tester configurations.

You start from the DMN, that you defined, here an example:

  lazy val InvoiceAssignApproverDMN = collectEntries(
    in = SelectApproverGroup(),
    out = Seq(ApproverGroup.management),
  )

Now you can add the following:

.testValues

Define the input values for the DMN you want to test.

For the following types this is done automatically:

If an input attribute is optional (Option) it also will have a null as a test input.

That said, you only need to define the rest of your inputs, like

  InvoiceAssignApproverDMN
    .testValues(_.amount, 249, 250, 999, 1000, 1001)

It starts with the name of the input (_.amount) and is followed by all test values with the according type.

The underline in _.amount is the input of the DMN (for the coder: it is a function: In => DmnValueType). This makes sure the compiler checks if there is such an attribute.

[error] -- [E008] Not Found Error: /Users/mpa/dev/Github/pme123/camundala/examples/invoice/camunda7/src/main/scala/camundala/examples/invoice/dmn/InvoiceDmnTesterConfigCreator.scala:27:20 
[error] 27 |      .testValues(_.amounts, 249, 250, 999, 1000, 1001),
[error]    |                  ^^^^^^^^^
[error]    |value amounts is not a member of camundala.examples.invoice.domain.SelectApproverGroup - did you mean _$1.amount?

.testUnit

By default, a DMN Test is integrated - meaning that it will take all dependent inputs into account.

So if you have complex set of dependent DMN Tables you can test them separately, like:

.testUnit

.dmnPath

To support different naming schemes, you can adjust the DMN file name the following way:

.acceptMissingRules

Sometimes you have a lot of rules that you don't want to test all. Adding .acceptMissingRules will allow missing rules in your test.

.inTestMode

When you validated a test result, you can create Test Cases. If you do so, you must add .inTestMode, otherwise the configuration will be overridden, when running the DMN Tester the next time.

Variables

If you have dynamic content in your DMN (input or output), you need to add them as well.

To distinguish them from testing inputs, we wrap them in a DmnVariable class.

Camunda DMN Engine handles Variables and Test Inputs exactly the same.

We distinguish them, because Variables are not important for the matching process. So we do not need to have different values for them.

We recommend not to use dynamic values in inputs of rules. If you do the variable will rather be a test input.

Example:

  case class Input(letters: String = "A_dynamic_2",
                   inputVariable: DmnVariable[String] = DmnVariable("dynamic"),
                   outputVariable: DmnVariable[String] = DmnVariable("dynamicOut")
                  )

Variables DMN

In this example the input must be A_dynamic_2 to match the first rule. So it is a corner case if this is rather a test input.

The output variable can be whatever you want.

Be aware that you must run the DMN Tester again, whenever you made changes (sbt dmn/run).

Configuration

See 03-dmn.

Problem Handling

The DMN Tester is run on Docker. So to find problems, you have:

If you are stuck, or find a problem, please create an issue on Github.