Process Testing

At this point you should already know how to create working processes. Because software tends to evolve over the years and might experience breaking changes, you should assure its integrity with process tests. As the name implies those tests are meant to run through your processes and act like a user might interact with them. By ensuring that the functionality still works the same way and does not change accidentally, these tests prevent you from introducing bugs and errors in your processes.

Setup Test Project

To get started you have to create a test project. The ‘Axon.ivy Test Project’ wizard will help you to create a test project with all required configurations plus a simple sample test.

  1. Add a new Axon.ivy Test Project

    • Right click on a project you would like to write tests for.

      Select: New -> Axon.ivy Test Project in the context menu.

    • Pick the test flavour IvyProcessTest to include in the ‘New Axon.ivy Test Project’ wizard.

    • Finish the wizard.

      A sample test will be generated into the src_test directory of the newly created project.

You now have a simple test called SampleIvyProcessTest.

Note a few things at this point:

Line 24:

The test class is annotated as an @IvyProcessTest, this enables you to run this test as a process tests.

Line 27:

As you want to test a specific process in this test class, the generated test class defines the BpmProcess under test in a constant called testee. The passed in String argument defines the process to run the test against. Replace MyProcess with a process that actually exists in your project under test.

Line 31:

The process under test can be directly started since it has only one start element. If your process under test contains multiple start elements you need to define the start element to be executed by using the BpmElement selector.

Line 30:

In each test method you have to pass in a BpmClient. This client is supplied by the process testing framework and represents an Axon.ivy Engine that can run and drive your processes along.

Write a Process Test

Now that everything is ready you can start writing your first actual process test. Let’s start by simply testing the following process:

../../_images/write-invoice-process.png

Execute a process

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
@IvyProcessTest
public class TestInvoiceProcess
{
  private static final BpmProcess INVOICE_PROCESS = BpmProcess.name("invoice");
  private static final BpmElement WRITE_INVOICE_START = INVOICE_PROCESS.elementName("writeInvoice.ivp");

  @Test
  void writeInvoice(BpmClient bpmClient)
  {
    ExecutionResult result = bpmClient
            .start()
            .process(WRITE_INVOICE_START)
            .execute();
    
    History history = result.history();
    assertThat(history.elementNames()).containsExactly("writeInvoice.ivp", "write invoice");
    
    Workflow workflow = result.workflow();
    assertThat(workflow.executedTask().getName()).isEqualTo("start write invoice");
  }

}

Now let us have a closer look at the code:

Line 4-7:

Here you tell your BpmClient that you want to test and execute your start element. After calling the execute method the BpmClient drives your process just after the first task.

Note

Note that the BpmClient does not run through the whole process at once but runs task by task. It also ignores skipTaskList flags and stops the execution at system tasks.

Line 12:

The Workflow API gives you access to the Case, Tasks as well as the Session of your executed process. Use it to fetch information about the active Case/Tasks, executed Tasks or the Session.

Line 15:

You have multiple APIs to assert your processes, one of it is the History. The History gives you access to the executed process elements, in this example we just assert the names of the executed elements.

Continue the process execution

As noted above the BpmClient does not run through the whole process at once, this means we now want to continue the current process.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Test
void writeInvoice(BpmClient bpmClient)
{
  ExecutionResult result = bpmClient
          .start()
          .process(WRITE_INVOICE_START)
          .execute();
  
  History history = result.history();
  assertThat(history.elementNames()).containsExactly("writeInvoice.ivp", "write invoice");
  
  Workflow workflow = result.workflow();
  assertThat(workflow.executedTask().getName()).isEqualTo("start write invoice");
  
  result = bpmClient
          .start()
          .anyActiveTask(result)
          .as().everybody()
          .execute();
  
  history = result.history();
  assertThat(history.elementNames())
          .containsExactly("write invoice", "writeInvoiceDialog()", "endWriteInvoiceDialog");
}

We introduced two new things in the code above:

Line 17:

To drive our process along you need to tell your BpmClient that it should just execute any active task. In this case there is only one possible active task that can be executed, the task from the UserTask element called write invoice.

Line 18:

Of course, you cannot just drive a process’s task along without declaring an appropriate Session. To declare said Session you can call the As method and append the desired Session, User or Role that should execute the next task.

Before you start writing test code we need to introduce you to two more concepts, mocking elements and asserting process data.

Mock dialogs and assert data

Process tests are not meant to assert UI elements such as Html Dialogs. Because dialogs are an important data input interface between users and your processes, you have to mock those inputs. If you want to test the dialogs themselves have a closer look at the Web Testing chapter.

Let us add a second test to assert your second process start checkOrder, which contains an Html Dialog you want to mock.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
private static final BpmElement CHECK_ORDER_START = INVOICE_PROCESS.elementName("checkOrder.ivp");

@Test
void checkOrder(BpmClient bpmClient)
{
  bpmClient.mock()
          .element(INVOICE_PROCESS.elementName("check order"))
          .with(Order.class, (in, out) -> out.setValid(Boolean.TRUE));
    
  ExecutionResult result = bpmClient
          .start()
          .process(CHECK_ORDER_START)
          .execute();
    
  Order orderData = result.data().last();
  assertThat(orderData.getValid()).isTrue();
}
Line 1:

Because you want to test the second process checkOrder you need to add another constant for its start. The process is available in our demo project.

Line 6:

Here you are telling the BpmClient that you are declaring a Mock for an element.

Line 7:

Here you select the element you want to mock by its name.

Line 8:

Your process uses the Order.ivyClass as its data class. When the execution reaches the declared element check order, it will return true for the valid field in its data.

Line 15-16:

With the Data API you can assert the process data of the executed elements.

Congratulations, you have learned about all necessary tools and most important APIs to ensure your process continuation. In the next section we will have a closer look at some of the APIs.

API Reference

The following section describes some of the more common API calls you can use. If you want to see the full functionality of each API you can follow the links in each subsection to the Public API.

Select

There are multiple ways to select processes and elements. The easiest way is to find them by their name.

/* Selecting processes */
BpmProcess.name("invoice");
BpmProcess.name().startsWith("invo");

/* Selecting process elements */
INVOICE_PROCESS.elementName("write invoice");
INVOICE_PROCESS.element().name().contains("write");

Start

To start an execution, you just need to tell the BpmClient which BpmProcess or BpmElement you want to run. To proceed with the execution, either tell the BpmClient to continue with the previous execution or choose the next desired task. Using a task is especially useful if there are multiple active tasks available.

ExecutionResult result = bpmClient.start()
        .process(WRITE_INVOICE_START)
        .execute();

/* Execute any active task after the previous result */
bpmClient.start()
        .anyActiveTask(result)
        .as().everybody()
        .execute();

/* Execute the desired task */
bpmClient.start()
        .task(result.workflow().anyActiveTask())
        .as().everybody()
        .execute();

As

Most processes require a specific user or role to be executed. You can define them by calling as.

/* Execution with the role everybody */
ExecutionResult result = bpmClient.start()
        .process(WRITE_INVOICE_START)
        .as().everybody()
        .execute();

/* Execution with a specific user */
result = bpmClient.start()
        .process(WRITE_INVOICE_START)
        .as().user("James Bond")
        .execute();

/* Execution by selecting a specific role */
result = bpmClient.start()
        .process(WRITE_INVOICE_START)
        .as().role("Everybody")
        .execute();

Mock

There are two ways of mocking an element. Either the element does not return anything, or the element returns some data. If your process runs through an Html Dialog you always need to mock it.

/* Mocking an element that doesn't require nor return anything */
bpmClient.mock()
        .element(INVOICE_PROCESS.elementName("check order"))
        .withNoAction();

/* Mocking without prior setup */
bpmClient.mock()
        .element(INVOICE_PROCESS.elementName("check order"))
        .with(Order.class, (in, out) -> out.setValid(Boolean.TRUE));

/* Mocking by defining the data beforehand */
Order myOrderData = new Order();
myOrderData.setValid(Boolean.TRUE);

bpmClient.mock()
        .element(INVOICE_PROCESS.elementName("check order"))
        .with(in -> myOrderData);

History

The History lets you assert the executed process elements. You can either assert the exact element objects or elements names.

/* Assert that a specific element has been executed */
assertThat(result.history().elements()).contains(WRITE_INVOICE_START);

/* Assert that the elements with the given names have been executed */
assertThat(result.history().elementNames()).contains("writeInvoice.ivp", "write invoice");

Workflow

The Workflow provides access to the active case or task. You can filter them by activator.

/* Asserting case and task states */
assertThat(result.workflow().activeCase().getState()).isEqualTo(CaseState.RUNNING);
assertThat(result.workflow().executedTask().getState()).isEqualTo(TaskState.DONE);

/* Asserting if any active tasks are present */
assertThat(result.workflow().anyActiveTask()).isPresent();
assertThat(result.workflow().activeTasks()).hasSize(1);
assertThat(result.workflow().activeTask().name("prepare shipment")).isPresent();
assertThat(result.workflow().activeTask().name().startsWith("prepare")).isPresent();

/* Asserting the activator */
assertThat(result.workflow().activeTask().activatorUser("JamesBond")).isPresent();
assertThat(result.workflow().activeTask().activator(user)).isPresent();

/* Asserting the session user */
assertThat(result.workflow().session().getSessionUserName()).isEqualTo("JamesBond");

Data

With the Data API you can assert the process data at different points in your process. You can get the data from the last executed element or from any element during execution. If an element is executed multiple times you can access the data of each execution in an ordered list.

/* Process data at last executed element */
assertThat(((Order)result.data().last()).getValid()).isFalse();

/* Process data at elements last execution */
assertThat(((Order)result.data().lastOnElement(writeInvoiceElement)).getValid()).isFalse();

/* Process data for each execution of the element */
assertThat(((Order)result.data().onElement(writeInvoiceElement).get(0)).getValid()).isFalse();