Pular para o conteúdo principal

Testando Seus Handlers

Um TaskHandler/AnswerProvider/DueDateProvider é uma classe Java comum — na maioria dos casos, ele não precisa do motor rodando para ser testado. Reserve testes com o motor real para verificar o fluxo do processo (roteamento, variáveis se propagando entre nós), não a lógica interna de cada handler.

Caminho recomendado: teste unitário isolado​

Mocke ExecutionContext e teste o handler como testaria qualquer outra classe — aqui, o CalcularScoreTaskHandler de Tratando Erros de Negócio, que rejeita um CPF malformado:

@Test
void lancaErroDeNegocioQuandoCpfInvalido() {
ExecutionContext execution = mock(ExecutionContext.class);
when(execution.getVariable("cpf"))
.thenReturn(new ProcessVariable("cpf", "cpf-invalido"));

CalcularScoreTaskHandler handler = new CalcularScoreTaskHandler(mock(BureauClient.class));

assertThrows(ProcessErrorException.class, () -> handler.handle(execution));
}

Sem Spring, sem motor, sem I/O — roda em milissegundos e é o lugar certo para cobrir as ramificações de lógica de negócio do handler (casos de borda, validações, cálculos).

Teste de integração: o fluxo completo com o motor real​

Quando você quer verificar que um processo inteiro se comporta como esperado — o roteamento de um gateway, a propagação de variáveis entre nós, o efeito de completar uma tarefa externa — suba um @SpringBootTest real trocando a persistência de produção pela implementação in-memory:

<dependency>
<groupId>io.kikwiflow</groupId>
<artifactId>kikwi-in-memory-spring-boot-starter</artifactId>
<version>${kikwiflow.version}</version>
<scope>test</scope>
</dependency>

O teste abaixo supõe um processo abertura-de-conta com uma tarefa síncrona CALCULAR_SCORE seguida da tarefa externa APROVAR_ABERTURA. O BureauClient é mockado — o teste verifica o fluxo, não a integração com o bureau:

@SpringBootTest
class AberturaDeContaProcessoIT {

@Autowired
private KikwiflowEngine kikwiflowEngine;

@Autowired
private KikwiEngineRepository repository;

@MockBean
private BureauClient bureauClient;

@Test
void concluiAposAnalistaAprovar() {
when(bureauClient.consultarScore("11111111111")).thenReturn(750);

ProcessInstance instancia = kikwiflowEngine.startProcess()
.byKey("abertura-de-conta")
.withBusinessKey("11111111111")
.withVariables(Map.of(
"cpf", new ProcessVariable("cpf", "11111111111"),
"rendaDeclarada", new ProcessVariable("rendaDeclarada", 8000)))
.execute();

// CALCULAR_SCORE é síncrona; a instância já está parada em APROVAR_ABERTURA.
assertEquals(ProcessInstanceStatus.ACTIVE, instancia.status());

String tarefaId = repository.findExternalTasksByProcessInstanceId(instancia.id()).stream()
.filter(t -> t.taskDefinitionId().equals("APROVAR_ABERTURA"))
.findFirst().orElseThrow()
.id();

ProcessInstance concluida = kikwiflowEngine.completeExternalTask(tarefaId,
Map.of("decisao", new ProcessVariable("decisao", "APROVADA")),
IdentityContext.system());

assertEquals(ProcessInstanceStatus.COMPLETED, concluida.status());
}
}

kikwi-in-memory-spring-boot-starter registra um KikwiEngineRepository in-memory automaticamente (via @ConditionalOnMissingBean), então basta ele estar no classpath de teste — nenhuma configuração adicional é necessária, e nenhum Mongo real é necessário para este teste.

Nós assíncronos (commitBefore: true) exigem esperar o poller

Diferente do caminho totalmente síncrono do exemplo acima, um nó com commitBefore: true é retomado por um worker interno que faz polling periódico (kikwiflow.execution.task-acquisition-interval-millis, default 5 segundos) — em um @SpringBootTest real esse poller está de fato rodando, então o teste precisa esperar (por exemplo, com Awaitility) até a instância alcançar o status esperado, em vez de assumir que o resultado já está pronto assim que .execute() retorna. É o caso, por exemplo, dos ramos de um PARALLEL_GATEWAY, que são sempre retomados em segundo plano.

Assertions mais expressivas (opcional)​

Se quiser um vocabulário de asserção mais rico do que inspecionar ProcessInstance na mão (assertThatProcessInstanceIsActive, assertHasActiveExternalTaskOn, assertThatProcessInstanceIsCompleted, etc.), adicione kikwi-core-testing como dependência de teste e substitua o bean de repositório pela sua AssertableKikwiEngine:

<dependency>
<groupId>io.kikwiflow</groupId>
<artifactId>kikwi-core-testing</artifactId>
<version>${kikwiflow.version}</version>
<scope>test</scope>
</dependency>
@TestConfiguration
class AssertableEngineTestConfig {
@Bean
@Primary
KikwiEngineRepository assertableKikwiEngine() {
return new AssertableKikwiEngine();
}
}
@SpringBootTest
@Import(AssertableEngineTestConfig.class)
class AberturaDeContaAssertableIT {

@Autowired
private KikwiflowEngine kikwiflowEngine;

@Autowired
private AssertableKikwiEngine assertableKikwiEngine;

@MockBean
private BureauClient bureauClient;

@Test
void paraEmAprovarAberturaAposCalcularScore() {
when(bureauClient.consultarScore("22222222222")).thenReturn(750);

ProcessInstance instancia = kikwiflowEngine.startProcess()
.byKey("abertura-de-conta")
.withBusinessKey("BK-TEST-2")
.withVariables(Map.of(
"cpf", new ProcessVariable("cpf", "22222222222"),
"rendaDeclarada", new ProcessVariable("rendaDeclarada", 9000)))
.execute();

assertableKikwiEngine.assertHasActiveExternalTaskOn(instancia.id(), "APROVAR_ABERTURA");
}
}

AssertableKikwiEngine implementa KikwiEngineRepository diretamente — como Spring escolhe o bean marcado @Primary, o resto da fiação (KikwiflowEngine, Navigator, ContinuationService) passa a operar sobre ele sem qualquer outra mudança de configuração.

A troca de bean é manual

Não há um @KikwiflowTest/test slice do Spring Boot que faça essa troca de bean automaticamente — o @TestConfiguration acima é curto, mas é algo que você mesmo declara no seu projeto.

Quando usar / quando não usar​

Use teste unitário para toda regra de negócio dentro de um handler — é rápido, e é onde os casos de borda cabem.

Use teste de integração com o motor para verificar o fluxo: um gateway roteando para o lado certo, uma variável chegando no nó seguinte, uma tarefa externa liberando a continuação. Não o use para reexercitar a lógica que o teste unitário já cobre.

Próximo passo​

Para ajustar o intervalo do poller e as demais propriedades que afetam testes com o motor real, veja Configuração Essencial.