Contracts in integration tests

Learn to create contracts to start processes or execute tasks in integration tests using the Bonita test toolkit.
There are two ways to create contracts: using the Java builder or from a JSon file in the classpath. The Java builder is handy for small contracts but can become tedious for large and complex contracts. On the other hand, it allows using dynamic values for contract inputs.

Integrations tests are built upon the Bonita Test Toolkit, based on the open source Bonita Java Client.
The Bonita Test Toolkit is only available for Enterprise, Performance, Efficiency, and Teamwork. editions.

Create contracts using the Java builder

The Java builder allows you to create contracts by specifying contract inputs one by one.
It is a pretty handy way to create small contracts, and it gives the possibility to use dynamic values for contract inputs (the persistence ID of a business object created during the test for example).
For large and static contracts it is recommended to store them in dedicated JSon files and to retrieve them from the classpath.

Simple contract inputs

Simple contract inputs are inputs of type Text, Integer, Double, Boolean, Date, LocalDate, LocalDateTime, OffsetDateTime and FileInputValue. Those inputs car be multiple or not.
Simple contract inputs can be added to the contract being built by using the corresponding Java method from the builder:

var contract = ContractBuilder.newContract()
    .textInput("textContractInput", "value")
    .multipleTextInput("multipleTextCntractInput", List.of("value1", "value2"))
    .integerInput("intergerContractInput", 1)
    .decimalInput("decimalContractInput", 1.0)
    .booleanInput("booleanContractInput", true)
    .dateInput("dateContractInput", new Date())
    .localDateInput("localDateContractInput", LocalDate.now())
    .localDateTimeInput("localDateTimeContractInput", LocalDateTime.now())
    .offsetDateTimeInput("offsetDateTimeContractInput", OffsetDateTime.now())
    .fileInput("fileContractInput", "classpathResourceURL") (1)
    .build();

var contractWithMultipleInputs = ContractBuilder.newContract()
    .multipleTextInput("multipleTextContractInput", List.of("value1", "value2"))
    .multipleIntegerInput("multipleIntegerContractInput", List.of(1, 2))
    .multipleDecimalInput("multipleDecimalContractInput", List.of(1.0, 2.0))
    .multipleBooleanInput("multipleBooleanContractInput", List.of(true, false))
    .multipleDateInput("multipleDateContractInput", List.of(new Date(), new Date()))
    .multipleLocalDateInput("multipleLocalDateContractInput", List.of(LocalDate.now(), LocalDate.now()))
    .multipleLocalDateTimeInput("multipleLocalDateTimeContractInput", List.of(LocalDateTime.now(), LocalDateTime.now()))
    .multipleOffsetDateTimeInput("multipleOffsetDateTimeContractInput", List.of(OffsetDateTime.now(), OffsetDateTime.now()))
    .multipleFileInput("multipleFileContractInput", List.of("classpathURL1", "classpathURL2"))
    .build();
1 For file contract inputs, the file to use must be added to the classpath, and the corresponding path has to be used in the contract builder. For example, put your files in src/test/resources/documents then classpathResourceURL should be set to /documents/myFile.

Complex contract inputs

A complex contract input has for value a list of contract inputs (simple or complex), It can be seen as a tree.
It is built like a simple contract input, but with a dedicated builder for the value:

ContractBuilder.newContract()
    .complexInput("complexContractInput", ComplexInputBuilder.complexInput()
        .localDateInput("localDateContractInput", LocalDate.now())
        .textInput("textContractInput", "value"))
    .build();

To build a contract with several levels of complex contract inputs, chain them in the builder:

ContractBuilder.newContract()
    .complexInput("parentComplexContractInput", ComplexInputBuilder.complexInput() (1)
        .localDateInput("localDateContractInput", LocalDate.now())
        .textInput("textContractInput", "value")
        .complexInput("childComplexContractInput", ComplexInputBuilder.complexInput() (2)
            .booleanInput("booleanContractInput", true)))
    .build();
1 The first level of contract input, the parent complex contract input which contains all the other contract inputs.
2 The second level of contract input, a child complex contract input which contains other contract inputs.

If a contract contains several levels of complex contract inputs with a lot of fields, using the Java builder to create the contract can become tedious. When a contract only contains static values, you may extract the contract in an external JSon file and retrieve them from the classpath to keep the test class concise.

Retrieve contracts from files in the classpath

Contracts can be stored in JSon files, and then retrieve at runtime from the classpath resources. This allows you to extract the contract definition and the values in a dedicated file, which is easier to read and maintain, especially for large and complex contracts.

A contract JSon file works as a key-value model, the key is the name of the contract input and the value of the contract input:

{
    "textContractInput" : "value",
    "booleanContractInput" : true,
    "dateContractInput" : "2021-10-10T00:00:00Z", (1)
	"complexContractInput" : {
		"integerContractInput" : 1,
	 	"localDateTimeContractInput" : "2021-02-10T12:23:32",
	},
	"multipleFileContractInput" : [
        "/documents/docA.txt",
		"/documents/docB.txt"
    ]
}
1 All dates (LocalDate, LocaDateTime, OffsetDateTime and Date) are passed as String using the ISO_8601 standard format.

Contract JSon files have to be available from the classpath at runtime.
To do so, put them for example in a folder src/test/resources/contracts/, and then use them in your tests:

var contract = ContractBuilder.newContract().fromClasspathResource("/contracts/<CONTRACT FILE NAME>.json");

Test contract constraints

A contract is made of contract inputs and constraints. Constraints are used to ensure the integrity of contracts inputs.
It is possible to validate in an integration test that for a given set of input a constraint fails:

In the following example, we have a contract for a business object Person, with two contract inputs: a name and a birthdate. We also have a constraint on the birthdate: it cannot be in the future.
The test validates that if the birthdate contract input is a date in the future, then the instantiation of the process fails.

@BonitaTests
class ProcessIT {

    @Test
    void should_fail_on_birthdate_constraint(BonitaTestToolkit toolkit) {
        User walter = toolkit.getUser("walter.bates");
        ProcessDefinition processDef = toolkit.getProcessDefinition("myProcess");

        var invalidContract = ContractBuilder.newContract()
            .complexInput("personContractInput", ComplexInputBuilder.complexInput()
                .textInput("name", "Adrien"))
                .localDateInput("birthdate", LocalDate.now().plusDays(1))
            .build();

        assertThatThrownBy(() -> processDef.startProcessFor(walter, invalidContract))
                        .isInstanceOf(StartProcessException.class)
                        .hasMessageContaining("The input [birthdate] cannot be in the futur!"); (1)
    }

}
1 The error message is the one defined in the technical message field of the constraint.