Skip to main content
Version: 10.3.1

Test Trigger Connector framework

The worker-task-test-trigger library is a part of the worker-task-test framework that provides execution and assertion APIs for testing Trigger Workers. This library allows you to configure and run Triggers, modify environment configurations and beans per test class, and assert Trigger results.

Since it is part of the worker-task-test framework, its API follows the same style as the worker-task-test-jnw library. Therefore, if you are already familiar with Java Native Worker (JNW) testing, writing tests with this library will be straightforward.

Integrate worker-task-test-trigger into project

To integrate worker-task-test-trigger into an existing or new project, follow the steps below, taking into account the JNW compatibility matrix:

  1. Add the version of worker-task-test to the <properties> section of your Maven project:

    <properties>
    <wf.worker-task-test.version>2.0.37</wf.worker-task-test.version>
    <!&#151;
    2.0.37 is an earliest version which supports features described in this article.
    You may need to look for recent version.
    &#151;>
    </properties>
  2. If your project is part of the workfusion-meta pipeline, add the worker-task-test dependency to the components.yaml file of the workfusion-meta project:

    your-project:
    dependencies:
    - worker-task-test
  3. Import worker-task-test-bom into the <dependencyManagement> section of your Maven project. Then, add any required module of worker-task-test-trigger-core to the <dependencies> section:

    <dependencyManagement>
    <dependencies>
    <dependency>
    <groupId>com.workfusion.jnw.test</groupId>
    <artifactId>worker-task-test-bom</artifactId>
    <version>${wf.worker-task-test.version}</version>
    <type>pom</type>
    <scope>import</scope>
    </dependency>
    </dependencies>
    </dependencyManagement>

    <dependencies>
    <dependency>
    <groupId>com.workfusion.jnw.test.trigger</groupId>
    <artifactId>worker-task-test-trigger-core</artifactId>
    <scope>test</scope>
    </dependency>
    </dependencies>

Prepare test class and modify execution context

To prepare a test class and modify its execution context, do as follows:

  1. Add the @TriggerJunitConfig annotation at the class level:

    @TriggerJunitConfig
    class TestClassName {

    // tests methods

    }
  2. Add the needed classes and packages.

  • If you have configuration classes with beans you want to override or reuse in your tests, add them to the classes property of the @TriggerJunitConfig annotation:

    @TriggerJunitConfig(classes = {FirstConfigurationClass.class, SecondConfigurationClass.class})
    class TestClassName {

    // tests methods

    }
  • If your configurations and beans are located in separate packages, add them to the basePackages property of the @TriggerJunitConfig annotation:

    @TriggerJunitConfig(basePackages = {"user.configuration.pacage", "user.beans.package"})
    class TestClassName {

    // tests methods

    }
    info

    You do not need to add the com.workfusion.spa, com.workfusion.connector.trigger, and com.workfusion.jnw.test.trigger packages to basePackages as they are included automatically.

    caution

    The framework automatically adds the test class package to ComponentScan. If the package name ends with task, the last part is removed. If your configuration exists in the package, it is added to the test context automatically. To exclude it, move your configuration to a different package.

  1. To modify or add properties in the application.yml configuration file, use the properties property in the @TriggerJunitConfig annotation:

    @TriggerJunitConfig(properties = {"it.test.value=testValue"})
    class TestClassName {

    // tests methods

    }

By default, you do not need to create application.yml as it is automatically generated in the target folder. If you you already have one, place it in the resources folder. If the file has a custom name, use this construct to modify the configuration file for the current test class:

@TriggerJunitConfig
@TestPropertySource(properties = {"spring.config.location = classpath:test-configuration.yml"})
class TestClassName {

// tests methods

}
Default application.yml file
spring:
config:
import:
- "optional:classpath:application.yml"
main:
web-application-type: none
application:
name: com-workfusion-jnw-test-triger-test
profiles:
active: execution-worker,trigger
cloud:
zookeeper:
config:
enabled: false
connection-timeout: 15000
session-timeout: 40000
vault:
enabled: false
management:
logging:
enabled: false
rabbitmq:
username: guest
virtual-host: vhost
password: guest
publisher-confirm-type: correlated
publisher-returns: true
cache:
connection:
mode: connection
wf:
sms:
local:
file:
path: ${java.io.tmpdir}/sms_local

connector:
uuid: 98a10042-7cb3-4221-b3ae-516ff3387aa7
processing:
monitoring:
initial-delay-seconds: 1
interval: 1

management:
logging:
enabled: false

Run and configure processor

To execute a processor, you can choose one of the following approaches:

  • TriggerTaskRunner: an abstraction over the Trigger processor class and Trigger configuration. It allows you to reuse configurations with different data provider states without duplicating the entire code. This approach results in a cleaner and more maintainable codebase.

  • SingleTriggerTaskLauncher: SingleTriggerTaskLauncher allows you to create tasks with configuration in one box.

TriggerTaskRunner approach

To initialize TriggerTaskRunner, use one of the options provided by TriggerTaskFactory:

  1. Initialize TriggerTaskRunner from the processor class without configuration:

    @Test
    void yourTest(TriggerTaskFactory taskFactory) {
    TriggerTaskRunner triggerTaskRunner = taskFactory.createTaskRunner(TaskProcessor.class);
    }
  2. Initialize TriggerTaskRunner using TriggerTaskInstance, which acts as a configuration builder (similar to SingleTriggerTaskLauncher) but cannot run without creating TriggerTaskRunner. In this case, TriggerTaskInstance prepares data and configuration for the runner, which can then be initialized as a class variable:

    TriggerTaskInstance taskInstance = new TriggerTaskInstance(TaskProcessor.class)
    .withConfiguration("inner json configuration")
    .withBpConfiguration("BP json configuration");

    @Test
    void yourTest(TriggerTaskFactory taskFactory) {
    TriggerTaskRunner triggerTaskRunner = taskFactory.createTaskRunner(taskInstance);
    }
  3. Initialize TriggerTaskRunner using the processor class and the TriggerTaskInstance unary operator.

    @Test
    void yourTest(TriggerTaskFactory taskFactory) {
    TriggerTaskRunner triggerTaskRunner = taskFactory.createTaskRunner(TaskProcessor.class, taskInstance -> taskInstance
    .withConfiguration("inner json configuration")
    .withBpConfiguration("BP json configuration"));
    }

SingleTriggerTaskLauncher approach

To run and configure a task with the SingleTriggerTaskLauncher builder, perform the following steps:

  1. Inject TriggerTaskFactory into your test method and call fromClass(Class<? Extend IPipelineProcessor>):

    @Test
    void yourTest(TriggerTaskFactory taskFactory) {
    //when
    SingleTriggerTaskLauncher singleTaskLauncher = taskFactory.fromClass(TaskProcessor.class);
    }
  2. Add configurations if required:

    @Test
    void yourTest(TriggerTaskFactory taskFactory) {
    //when
    SingleTriggerTaskLauncher singleTaskLauncher = taskFactory.fromClass(TaskProcessor.class)
    .withConfiguration("inner json configuration")
    .withBpConfiguration("BP json configuration");
    }
  3. Run the launcher using the same methods as in TriggerTaskRunner:

    @Test
    void yourTest(TriggerTaskFactory taskFactory) {
    //when
    TriggerOutput output = taskFactory.fromClass(TaskProcessor.class)
    .withConfiguration("inner json configuration")
    .withBpConfiguration("BP json configuration")
    .runWithTimeout(8L, TimeUnit.SECONDS);
    }

Execute TriggerTaskRunner or SingleTriggerTaskLauncher

A Trigger can run continuously, for example, when monitoring an external source or using scheduled polling. In such cases, instead of the input Trigger testing framework, use stop rules to limit execution.

There are two types of stop rules:

  • TimeoutTriggerStopRule
  • RecordsTriggerStopRule

TriggerTaskRunner and SingleTriggerTaskLauncher share the same execution launch methods, so all methods described below apply to both.

TimeoutTriggerStopRule

This rule defines a time period during which the Trigger runs before returning results.

To start execution with TimeoutTriggerStopRule, use one of the following methods:

  • TriggerStopRuleBuilder:

    @Test
    void yourTest(TriggerTaskFactory taskFactory) {
    //when
    TriggerOutput output = taskFactory.fromClass(TaskProcessor.class)
    .runWithTimeoutStopRule(timeoutTriggerStopRule -> timeoutTriggerStopRule.withTimeout(5L, TimeUnit.SECONDS));
    }
  • Predefined methods for TriggerStopRule:

    @Test
    void yourTest(TriggerTaskFactory taskFactory) {
    //when
    TriggerOutput first = taskFactory.fromClass(TaskProcessor.class).runWithTimeout(5L, TimeUnit.SECONDS);
    TriggerOutput second = taskFactory.fromClass(TaskProcessor.class).runWithTimeout(1500L); // this method use MILLISECONDS as time unit
    }

RecordsTriggerStopRule

RecordsTriggerStopRule is a more complex rule that allows you to configure each rule parameter as execution time, check time interval, and timeout and even define a custom stop rule.

Available rule parameters and methods are as follows:

  • delay: time before the rule is first checked.

    @Autowired
    TriggerStopRuleFactory ruleFactory;

    @Test
    void yourTest(TriggerTaskFactory taskFactory) {
    //when
    ruleFactory.recordsTriggerStopRule()
    .withDelay(10L) // default time unit is SECONDS
    .withDelayUnit(TimeUnit.MINUTES)
    .withDelay(10000L, TimeUnit.MILLISECONDS);
    }
  • interval: how often the rule is checked afterward.

    @Autowired
    TriggerStopRuleFactory ruleFactory;

    @Test
    void yourTest(TriggerTaskFactory taskFactory) {
    //when
    ruleFactory.recordsTriggerStopRule()
    .withInterval(10L) // default time unit is SECONDS
    .withIntervalTimeUnit(TimeUnit.MINUTES)
    .withInterval(10000L, TimeUnit.MILLISECONDS);
    }
  • timeout: total execution duration before stopping.

    @Autowired
    TriggerStopRuleFactory ruleFactory;

    @Test
    void yourTest(TriggerTaskFactory taskFactory) {
    //when
    ruleFactory.recordsTriggerStopRule()
    .withTimeout(10L) // default time unit is SECONDS
    .withTimeoutUnit(TimeUnit.MINUTES)
    .withTimeout(10000L, TimeUnit.MILLISECONDS);
    }
  • rule: Predicate that stops execution when it returns true. As a rule parameter, it receives TriggerRecordsContainer describing the current result state. You can use data provider states, counters, or other logic in this rule.

    @Autowired
    TriggerStopRuleFactory ruleFactory;
    @MockBean
    CustomDataProvider dataProvider;

    @Test
    void yourTest(TriggerTaskFactory taskFactory) {
    //when
    ruleFactory.recordsTriggerStopRule()
    .withRule(container -> container.getRecords().size() >= 5)
    .withRule(container -> dataProvider.getDoneFlag() == true);
    }
info

RecordsTriggerStopRule provides the withTimeUnit(TimeUnit timeUnit) method to set a common time unit for all parameters.

To start execution with RecordsTriggerStopRule, you can use one of the foolowing methods:

  • TriggerStopRuleBuilder:

    @Test
    void yourTest(TriggerTaskFactory taskFactory) {
    //when
    TriggerOutput output = taskFactory.fromClass(TaskProcessor.class)
    .runWithRecordsStopRule(timeoutTriggerStopRule -> timeoutTriggerStopRule
    .withTimeout(10L, TimeUnit.SECONDS)
    .withRule(container -> container.getRecords().size() >= 5));
    }
  • Predefined methods for TriggerStopRule:

    @Test
    void yourTest(TriggerTaskFactory taskFactory) {
    //when
    TriggerOutput first = taskFactory.fromClass(TaskProcessor.class).runWithRecordsLimit(5);
    }

Asynchronous methods

All methods described above have alternative asynchronous methods with the Async suffix. Asynchronous methods return Future<TriggerOutput> instead of TriggerOutput, allowing you to avoid waiting for the Trigger results and, for example, start the next one or change the behavior of the data provider during Trigger execution.

To get execution results, call the get() method, which returns the result if it is available or interrupts the thread until it is ready.

@MockBean
CustomDataProvider dataProvider;

@Test
void yourTest(TriggerTaskFactory taskFactory) {
//when
Future<TriggerOutput> output = taskFactory.fromClass(TaskProcessor.class)
.runWithRecordsStopRuleAsync(timeoutTriggerStopRule -> timeoutTriggerStopRule
.withTimeout(10L, TimeUnit.SECONDS)
.withRule(container -> dataProvider.getDoneFlag() == true));

Thread.sleep(2000);
dataProvider.addRow("some data");

TriggerOutput result = output.get();
}

TriggerOutput

TriggerOutput is a simple collector of RecordResult that your Trigger sends during execution. At the same time, it provides several additional methods that allow you to iterate by results or filter them with a custom rule:

  • getAllTaskResults() returns a collection of all RecordResult objects.
  • getIteratorByResult() returns Iterator<RecordResult> over all records.
  • filterResult(Predicate<RecordResult> filter) returns a filtered list of results (List<RecordResult>).

Assert TriggerOutput

The main goal of worker-task-test-trigger-assertions is to provide a convenient way to check the status of RecordResult objects, filter by status, and use the AssertJ-based RecordResult instance for assertion.

To perform assertions, call the static assertThat method from the TriggerOutputAssert class. It supports both Collection<RecordResult> and TriggerOutput, producing equivalent results:

    @Test
void yourTest(TriggerTaskFactory taskFactory) {
TriggerOutput result = taskFactory.fromClass(TaskProcessor.class).runWithRecordsLimit(5);
TriggerOutputAssert.assertThat(result).isSuccessful();
}

You can check or filter results by status using the following methods:

  • For the COMPLETED status:
    • isSuccessful() verifies that all records have the same status.
    • containSuccessfulRecordResult() checks if at least one record is COMPLETED.
    • doesNotContainSuccessfulRecordResult() ensures no records are COMPLETED.
    • isPartiallySuccessful() checks if at least one record is COMPLETED and returns AssertJ-based ListRecordResultAssert for such records. In this case, you can work with it as with a simple list assertion.
  • For FAILED, INVALID_REQUEST, NOT_AUTHORIZED, and NOT_FOUND statuses:
    • isFailed() checks if all records have the same status.
    • containFailedRecordResult() verifies if at least one record has the error status.
    • doesNotContainFailedRecordResult() checks if the result list does not contain any record with the error status.
    • isPartiallyFailed() verifies if at least one record has the error status and returns AssertJ-based ListIncompleteRequestResultAssert for such records. In this case, you can work with it as with a simple list assertion.
    • isPartialFailed(RecordProcessingStatus failureStatus) is the same as isPartiallyFailed() but you can choose the status you need.
  • For IN_PROGRESS status:
    • isInProgress() ensures all records have the same status.
    • containInProgressRecordResult() checks if at least one record has the IN_PROGRESS status.
    • doesNotContainInProgressRecordResult() checks if the result list does not contain records with the IN_PROGRESS status.
    • isPartiallyInProgress() checks if at least one record has the IN_PROGRESS status and returns AssertJ-based ListIncompleteRequestResultAssert for such records. In this case, you can work with it as with a simple list assertion.

You can call any methods that the AbstractCollectionAssert class from AssertJ provides for asserting collections.

If you want to assert all RecordResult objects as a list, call the asListOfTriggerResult() method or use asListOfTriggerResultByStatus(RecordProcessingStatus... statuses) and choose only records with a specific status, for example, only successful and failed records.

    @Test
void yourTest(TriggerTaskFactory taskFactory) {
TriggerOutput result = taskFactory.fromClass(TaskProcessor.class).runWithRecordsLimit(5);

TriggerOutputAssert.assertThat(result).asListOfTriggerResultByStatus(RecordProcessingStatus.COMPLETED, RecordProcessingStatus.FAILED)
.anySatisfy(recordResult -> {
assertThat(recordResult.getData()).hasSize(2);
});
}

RequestResult actually has some specific assertions for its values:

ParameterMethods
requestId
  • isRequestIdEqualTo(String requestId)
  • isRequestIdNotEqualTo(String requestId)
  • isRequestIdEqualTo(UUID requestId)
  • isRequestIdNotEqualTo(UUID requestId)
  • requestIdAsString()
format
  • formatAsString()
  • isFormatEqualTo(String format)
  • isFormatNotEqualTo(String format)
  • isFormatNull()
  • isFormatNotNull()
statusDetails
  • isStatusDetailsAsString()
  • isStatusDetailsIsEqual(String statusDetails)
  • isStatusDetailsIsNotEqual(String statusDetails)
  • isStatusDetailsIsNull()
  • isStatusDetailsNotNull()
  • isStatusDetailsIsEmpty()
  • isStatusDetailsIsNotEmpty()
status
  • isStatusEqualTo(RecordProcessingStatus status)
  • isStatusNotEqualTo(RecordProcessingStatus status)
  • isStatusIn(RecordProcessingStatus... statuses)
data
  • hasData() returns DataAssert, which is a wrapper around AbstractListAssert for List<Map<String,Object>.
  • dataIsEqualTo(List<Map<String, Object>> data)
  • dataIsNotEqualTo(List<Map<String, Object>> data)
  • dataIsNull()
  • dataIsNotNull()
  • dataHasOneElement() return MapAssert<String, Object>
  • dataIsEmpty()
  • dataIsNotEmpty()
  • dataHasSize(int size)

See additional examples of assertions. If you are familiar with the AssertJ API, you will have no trouble writing assertions for TriggerOutput and RecordResult:

    @Test
void yourTest(TriggerTaskFactory taskFactory) {
TriggerOutput result = taskFactory.fromClass(TaskProcessor.class).runWithRecordsLimit(5);

TriggerResultAssert.assertThat(results).doesNotContainFailedRecordResult().isPartiallySuccessful()
.allSatisfyAssert(assertion ->
assertion.isFormatNull().hasData().singleElement().isNotEmpty().allSatisfy((key, value) ->
assertThat(value).isEqualTo("some_data")));

TriggerOutputAssert.assertThat(result).isSuccessful().allSatisfyAssert(assertion ->
assertion.dataHasOneElement().containsAnyOf(data.entrySet().toArray(new Map.Entry[0])));
}
warning

The data parameter of RecordResult has the List<Map<String,Object>> format, and you need to check the list first and only then assert the map with the data.

Use additional modules

The worker-task-test library includes optional modules that can be useful for specific scenarios, such as database interaction, S3 support, or when working with the secret storage. The modules are not required for running task processors.

Available modules include:

ORMLite support module

The module enables you to mock ORMLite repositories in your tests. You can create repositories, populate them with data, and verify the state of the repository after processor execution.

The approach is similar to testing with the ODF 2 Data Store. For more details, refer to Bot Task JUnit | ODF 2: ORM and Entity classes.

Setup

To enable the feature, add the worker-task-test-orm-lite-module dependency to your project with a test scope and define the module version in worker-task-test-bom:

<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.workfusion.jnw.test</groupId>
<artifactId>worker-task-test-bom</artifactId>
<version>${wf.worker-task-test.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<dependency>
<groupId>com.workfusion.jnw.test.trigger</groupId>
<artifactId>worker-task-test-trigger-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.workfusion.jnw.test</groupId>
<artifactId>worker-task-test-orm-lite-module</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

Usage

To get started with ORMLite repositories, add the OrmLiteSupport parameter to your test method signature:

    private static OrmLiteRepository<User> userRepository;

@BeforeAll
static void prepareDocument(OrmLiteSupport ormLiteSupport) {
ormLiteSupport.createTables(User.class, CustomRepository.class);

userRepository = ormLiteSupport.getRepository(User.class);

userRepository.create(new User(1, "firstName"))
}

@Test
void shouldGetDocumentByUuidWithRepositoryFromCode(TriggerTaskFactory taskFactory, OrmLiteSupport ormLiteSupport) {
//given
ormLiteSupport.createTables(Document.class);
OrmLiteRepository<Document> documentRepository = ormLiteSupport.getRepository(Document.class);

documentRepository.create(newDocument);
documentRepository.create(document);

//when
TriggerOutput result = taskFactory.fromClass(TaskProcessor.class).runWithRecordsLimit(5);

// Data Store test
Assertions.assertThat(documentRepository.count()).isEqualTo(2);
Assertions.assertThat(documentRepository.findAll()).containsOnly(document, newDocument);
}
note

The table's lifecycle depends on the test scope in which it is created. For example, if you create a table in the BeforeAll scope, it is available across all tests. However, if you create it within a specific test, it only exists for the duration of that test.

OrmLiteSupport provides the following methods used for working with tables:

  • createTables(Class<?>... entities) creates tables.

  • clearTables(Class<?>... entities) and clearAllCreatedTables() clear tables.

  • dropTables(Class<?>... entities) and dropAllCreatedTables() drop tables.

  • getRepository(Class<T> entity) retrieves a table repository.

dropAllCreatedTables() and clearAllCreatedTables() only affect the current test scope. If you call these methods inside a test method, they clean up or delete only the tables created within that test; tables created in the Before or After scopes remain unaffected.

warning

worker-task-test-orm-lite-module requires both DataSource and ConnectionSource (or DatabaseType) beans. By default, the module provides only the ConnectionSource bean, which modifies all table names using the versioned template from DigitalWorkerInfo. The DataSource bean is not included. You must define it manually or use one provided by the jnw-toolkit-datastores module.

note

If you do not want to set default ConnectionSource or DatabaseType, you can add the worker-task-test.connection-source.provide=false or worker-task-test.database-type.provide=false properties.

If you use multiple AI Agents in the same test class and need to manage repositories for each one individually, use MultiDigitalWorkerOrmLiteSupport instead of OrmLiteSupport in your test methods. The utility allows you to handle repositories just like OrmLiteSupport. At the same time, you can add a DigitalWorkerInfo object to the method and create a repository based on the data from the AI Agent information. Methods that do not require a DigitalWorkerInfo object as a parameter use the default DigitalWorkerInfo component or the one provided in the test context.

S3 module

The module provides a mechanism for mocking an Amazon S3 service for use in your tests.

It works similarly to the ODF 2 S3 mocking mechanism. For more details, refer to Bot Task JUnit | S3 mocking.

Setup

To enable the feature, add the worker-task-test-s3-module dependency to your project with the test scope and define the module version in worker-task-test-bom:

<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.workfusion.jnw.test</groupId>
<artifactId>worker-task-test-bom</artifactId>
<version>${wf.worker-task-test.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<dependency>
<groupId>com.workfusion.jnw.test.trigger</groupId>
<artifactId>worker-task-test-trigger-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.workfusion.jnw.test</groupId>
<artifactId>worker-task-test-s3-module</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

Usage

For each test run, an in-memory S3 server is automatically initialized to handle user requests. To interact with the server, inject the com.workfusion.jnw.test.s3.S3MockClient object into any test class method annotated with @Test, @BeforeAll, @BeforeEach, @AfterEach, or @AfterAll:

    @BeforeAll
static void prepareDocument(S3MockClient s3Client) {
s3Client.createBucket("test-jnw-bucket");
}

@Test
void shouldGetDocumentByUuidWithRepositoryFromCode(TriggerTaskFactory taskFactory, S3MockClient s3Client) {
// given
s3Client.createBucket("jnw-bucket");
s3Client.putObject("jnw-bucket", "test_content.txt", "test_content");

// when
TriggerOutput result = taskFactory.fromClass(TaskProcessor.class).runWithRecordsLimit(5);

// then
TriggerResultAssert.assertThat(results).doesNotContainFailedRecordResult().isPartiallySuccessful()
.allSatisfyAssert(assertion ->
assertion.isFormatNull().hasData().singleElement().isNotEmpty().allSatisfy((key, value) ->
assertThat(value).isEqualTo("test_content")));
Assertions.assertThat(s3Client.getObjectAsString("jnw-bucket", "test_content.txt")).isEqualTo("test_content");
}

Explore the S3MockClient interface to view all methods and their descriptions.

Secret module

The module provides a mechanism for working with the secret storage in your tests. Its main purpose is to offer a convenient API for creating, updating, and clearing SecretDto objects in your tests.

Setup

To enable the feature, add the worker-task-test-secret-module dependency to your project with the test scope and define the module version in worker-task-test-bom:

<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.workfusion.jnw.test</groupId>
<artifactId>worker-task-test-bom</artifactId>
<version>${wf.worker-task-test.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<dependency>
<groupId>com.workfusion.jnw.test.trigger</groupId>
<artifactId>worker-task-test-trigger-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.workfusion.jnw.test</groupId>
<artifactId>worker-task-test-secret-module</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

Usage

For each test run, an in-memory secret storage service is initialized. To interact with the service, inject the JnwSecretService object into any test class method annotated as @Test, @BeforeAll, @BeforeEach, @AfterEach, or @AfterAll:

    private final SecretDto beforeEachSecret = new SecretDto("beforeAlias", "beforeKey", "beforeValue", new Date());

@BeforeEach
void beforeEach(JnwSecretService secretService) {
secretService.createSecret(beforeEachSecret.getAlias(), beforeEachSecret.getKey(), beforeEachSecret.getValue());
}

@Test
@DisplayName("should read secret dto from secret service")
void shouldReadSecretDtoFromSecretService(TriggerTaskFactory taskFactory, JnwSecretService secretService) {
//given
final String secretAlias = "alias";
final String secretKey = "key";
final String secretValue = "value";

secretService.createSecret(secretAlias, secretKey, secretValue);

//when
TriggerOutput result = taskFactory.fromClass(TaskProcessor.class).runWithRecordsLimit(5);

//then
TriggerOutputAssert.assertThat(results).isSuccessful().allSatisfyAssert(successfulRequestResultAssert ->
successfulRequestResultAssert.dataHasOneElement()
.containsEntry("key", secretKey)
.containsEntry("value", secretValue);
}

JnwSecretService provides the following methods for working with SecretDto objects:

  • createSecret(String alias, String key, String value) creates a SecretDto object.

  • updateSecret(String alias, String key, String value) updates a SecretDto object.

  • deleteSecret(String alias) deletes a SecretDto object.

  • getSecret(String alias) retrieves a SecretDto object from SecretService.

The module's scope mechanism works the same way as in the previous modules. A secret created within a test is only available in that test, whereas a secret created in the Before or After scopes is available across all tests.

warning

Do not override global secrets within a test scope as they will be deleted once the test completes.

View code examples

SamplePipelineProcessorWithMockServiceTest.class
package com.workfusion.jnw.test.processor;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;

import com.workfusion.connector.input.api.RecordResult;
import com.workfusion.jnw.configuration.TestConfiguration;
import com.workfusion.jnw.test.pojo.TaskConfiguration;
import com.workfusion.jnw.test.service.MockDataService;
import com.workfusion.jnw.test.trigger.assertions.TriggerOutputAssert;
import com.workfusion.jnw.test.trigger.junit.TriggerJunitConfig;
import com.workfusion.jnw.test.trigger.launcher.TriggerTaskFactory;
import com.workfusion.jnw.test.trigger.launcher.runner.TriggerTaskLauncher;

@TriggerJunitConfig(classes = {TestConfiguration.class})
public class SamplePipelineProcessorWithMockServiceTest {

private static final Map<String, Object> data = new HashMap<>();
private static TriggerTaskLauncher taskLauncher;
private final ObjectMapper objectMapper = new ObjectMapper();

@Autowired
public MockDataService dataService;

@BeforeAll
static void init(TriggerTaskFactory taskFactory) {
taskLauncher = taskFactory.createTaskRunner(SamplePipelineProcessor.class);
for(int index = 0; index < 10; index++) {
data.put(UUID.randomUUID().toString(), String.format("The data of record is equal of index: %d", index));
}
}

@BeforeEach
void setUp() {
dataService.cleanup();
dataService.addData(data);
}

@Test
@DisplayName("should run taskLauncher without configuration")
void shouldRunTaskLauncherWithoutConfiguration() {
TriggerOutputAssert.assertThat(taskLauncher.run()).isSuccessful().allSatisfyAssert(successfulRequestResultAssert ->
successfulRequestResultAssert.dataHasOneElement().containsAnyOf(data.entrySet().toArray(new Map.Entry[0])));
}

@Test
@DisplayName("should execute trigger from class with timeout")
void shouldExecuteTriggerFromClass(TriggerTaskFactory taskFactory) throws JsonProcessingException {
//given
final TaskConfiguration taskConfiguration = new TaskConfiguration(3, 500L);
final Collection<RecordResult> results = taskFactory.fromClass(SamplePipelineProcessor.class)
.withConfiguration(objectMapper.writeValueAsString(taskConfiguration))
.runWithTimeout(5L, TimeUnit.SECONDS)
.getAllTaskResults();

//then
TriggerOutputAssert.assertThat(results).isSuccessful().allSatisfyAssert(successfulRequestResultAssert ->
successfulRequestResultAssert.dataHasOneElement().containsAnyOf(data.entrySet().toArray(new Map.Entry[0])));
}

}
SamplePipelineProcessorTest.class
package com.workfusion.jnw.test.processor;

import java.util.Collection;
import java.util.concurrent.TimeUnit;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.workfusion.connector.input.api.RecordResult;
import com.workfusion.jnw.test.pojo.TaskConfiguration;
import com.workfusion.jnw.test.trigger.assertions.TriggerResultAssert;
import com.workfusion.jnw.test.trigger.junit.TriggerJunitConfig;
import com.workfusion.jnw.test.trigger.launcher.TriggerTaskFactory;
import com.workfusion.jnw.test.trigger.launcher.runner.TriggerTaskLauncher;
import com.workfusion.jnw.test.trigger.launcher.stoprule.TriggerStopRule;
import com.workfusion.jnw.test.trigger.launcher.stoprule.TriggerStopRuleFactory;

import static org.assertj.core.api.Assertions.assertThat;

@TriggerJunitConfig
class SamplePipelineProcessorTest {

private static TriggerTaskLauncher taskLauncher;
private final ObjectMapper objectMapper = new ObjectMapper();
private static final Logger logger = LoggerFactory.getLogger(SamplePipelineProcessorTest.class);

@BeforeAll
static void init(TriggerTaskFactory taskFactory) {
taskLauncher = taskFactory.createTaskRunner(SamplePipelineProcessor.class);
}

@Test
@DisplayName("should run taskLauncher without configuration")
void shouldRunTaskLauncherWithoutConfiguration() {
assertResults(taskLauncher.run().getAllTaskResults());
}

@Test
@DisplayName("should execute trigger from class with timeout")
void shouldExecuteTriggerFromClass(TriggerTaskFactory taskFactory) throws JsonProcessingException {
//given
final TaskConfiguration taskConfiguration = new TaskConfiguration(3, 500L);
final Collection<RecordResult> results = taskFactory.fromClass(SamplePipelineProcessor.class)
.withConfiguration(objectMapper.writeValueAsString(taskConfiguration))
.runWithTimeout(8L, TimeUnit.SECONDS)
.getAllTaskResults();

//then
assertResults(results);
}

@Test
@DisplayName("should execute trigger with records limit")
void shouldExecuteTriggerWithRecordsLimit() throws JsonProcessingException {
//given
final TaskConfiguration taskConfiguration = new TaskConfiguration(1, 1000L);

//when
final Collection<RecordResult> results = taskLauncher.withConfiguration(objectMapper.writeValueAsString(taskConfiguration)).runWithRecordsLimit(1).getAllTaskResults();

//then
assertResults(results);

}

@Test
@DisplayName("should execute trigger with timeout rule")
void shouldExecuteTriggerWithTimeoutRule() {
//when
final Collection<RecordResult> results = taskLauncher.runWithTimeoutStopRule(timeoutTriggerStopRule -> timeoutTriggerStopRule.withTimeout(5L, TimeUnit.SECONDS)).getAllTaskResults();

//then
assertResults(results);
}

@Test
@DisplayName("should execute trigger with records rule")
void shouldExecuteTriggerWithRecordsRule() {
//when
Collection<RecordResult> results = taskLauncher.runWithRecordsStopRule(recordsTriggerStopRule -> recordsTriggerStopRule.withDelay(0L)
.withInterval(1L)
.withRule(container -> container.getRecords().size() >= 2)).getAllTaskResults();

//then
assertResults(results);
}

@Test
@DisplayName("should execute trigger with timeout in millis")
void shouldExecuteTriggerWithTimeoutInMillis() {
//when
final Collection<RecordResult> results = taskLauncher.runWithTimeout(8000L).getAllTaskResults();

//then
assertResults(results);
}

@Test
@DisplayName("should throw exception if rule timeout is done")
void shouldThrowExceptionIfRuleTimeoutIsDone(TriggerStopRuleFactory stopRuleFactory) {
//given
final TriggerStopRule rule = stopRuleFactory.recordsTriggerStopRule()
.withTimeout(2L)
.withRule(container -> false)
.build();

//then
Assertions.assertThatThrownBy(() -> taskLauncher.runWithStopRule(rule)).isInstanceOf(IllegalStateException.class).hasMessageStartingWith("Trigger execution failed with timeout");
}

@Test
@DisplayName("should throw exception if rule failed")
void shouldThrowExceptionIfRuleFailed(TriggerStopRuleFactory stopRuleFactory) {
//given
final TriggerStopRule rule = stopRuleFactory.recordsTriggerStopRule()
.withDelay(1L)
.withRule(container -> {
throw new IllegalArgumentException("Test message");
})
.build();

//then
Assertions.assertThatThrownBy(() -> taskLauncher.runWithStopRule(rule))
.isInstanceOf(IllegalStateException.class)
.hasMessageStartingWith("Rule execution failed with error: ");
}

public static boolean assertResults(Collection<RecordResult> results) {
logger.warn("Record size in result is: {}", results.size());
TriggerResultAssert.assertThat(results).doesNotContainFailedRecordResult().isPartiallySuccessful()
.allSatisfyAssert(assertion -> assertion.isFormatNull().hasData().singleElement().isNotEmpty().allSatisfy((key, value) -> assertThat(value).isEqualTo("some_data")));
return true;
}
}