Skip to content

12 - Testing Strategy

This document specifies the test plan, test cases, mocking approach, and testing conventions.

DependencyPurpose
JUnit 5 (Jupiter)Test framework
AssertJFluent assertions
MockitoMocking ChatLanguageModel, tools, etc.
SLF4J Simple or Logback TestLogging during tests
  • Test class naming: {ClassName}Test.java for unit tests, {Feature}IntegrationTest.java for integration tests
  • Test method naming: testMethodName_scenario_expectedBehavior or descriptive @DisplayName
  • Structure: Arrange / Act / Assert pattern
  • No test order dependencies: Each test is independent and self-contained
  • No real network calls: All LLM interactions are mocked
  • Deterministic: No reliance on time, randomness, or external state
agentensemble-core/src/test/java/io/agentensemble/
AgentTest.java # Agent builder validation
TaskTest.java # Task builder validation
EnsembleTest.java # Ensemble validation (run() preconditions)
agent/
AgentExecutorTest.java # Agent execution with mocked LLM
AgentPromptBuilderTest.java # Prompt construction
workflow/
SequentialWorkflowExecutorTest.java # Sequential execution logic
tool/
LangChain4jToolAdapterTest.java # Tool adaptation
ToolResultTest.java # ToolResult factory methods
config/
TemplateResolverTest.java # Template variable substitution
integration/
SequentialEnsembleIntegrationTest.java # End-to-end sequential ensemble
ToolUseIntegrationTest.java # Agent with tools, mocked LLM
ErrorHandlingIntegrationTest.java # Error scenarios end-to-end

LangChain4j’s ChatLanguageModel is mocked using Mockito:

ChatLanguageModel mockLlm = mock(ChatLanguageModel.class);
// For simple text response (no tools):
when(mockLlm.generate(anyList()))
.thenReturn(Response.from(AiMessage.from("Mocked response text")));
// For tool call response:
when(mockLlm.generate(anyList(), anyList()))
.thenReturn(Response.from(AiMessage.from(
ToolExecutionRequest.builder()
.name("web_search")
.arguments("{\"input\": \"AI trends 2026\"}")
.build())))
.thenReturn(Response.from(AiMessage.from("Final answer after tool use")));

For AgentTool implementations:

AgentTool mockTool = mock(AgentTool.class);
when(mockTool.name()).thenReturn("web_search");
when(mockTool.description()).thenReturn("Search the web");
when(mockTool.execute(anyString())).thenReturn(ToolResult.success("Search results..."));
TestDescription
testBuild_withAllFields_succeedsBuild with every field set, verify all values
testBuild_withMinimalFields_succeedsBuild with only role + goal + llm
testBuild_withNullRole_throwsValidationNull role throws ValidationException
testBuild_withBlankRole_throwsValidationBlank/empty role throws ValidationException
testBuild_withNullGoal_throwsValidationNull goal throws ValidationException
testBuild_withBlankGoal_throwsValidationBlank goal throws ValidationException
testBuild_withNullLlm_throwsValidationNull LLM throws ValidationException
testBuild_withZeroMaxIterations_throwsValidationmaxIterations=0 throws
testBuild_withNegativeMaxIterations_throwsValidationmaxIterations=-1 throws
testBuild_withInvalidToolObject_throwsValidationObject that is not AgentTool and has no @Tool methods
testBuild_withAgentToolAndAnnotatedTool_succeedsMixed tool types in same list
testDefaultValues_areCorrectVerify verbose=false, maxIterations=25, etc.
testToBuilder_createsModifiedCopyModify one field via toBuilder, others unchanged
testToolsList_isImmutableVerify tools list cannot be modified after build
TestDescription
testBuild_withAllFields_succeedsBuild with every field set
testBuild_withMinimalFields_succeedsBuild with only description + expectedOutput + agent
testBuild_withNullDescription_throwsValidationNull description throws
testBuild_withBlankDescription_throwsValidationBlank description throws
testBuild_withNullExpectedOutput_throwsValidationNull expectedOutput throws
testBuild_withBlankExpectedOutput_throwsValidationBlank expectedOutput throws
testBuild_withNullAgent_throwsValidationNull agent throws
testBuild_withSelfReference_throwsValidationTask in its own context list
testBuild_withEmptyContext_succeedsDefault empty context list
testContextList_isImmutableVerify context list cannot be modified after build

Tests call run() which triggers validation before execution.

TestDescription
testRun_withEmptyTasks_throwsValidationNo tasks throws
testRun_withEmptyAgents_throwsValidationNo agents throws
testRun_withUnregisteredAgent_throwsValidationTask references agent not in ensemble
testRun_withCircularContext_throwsValidationTasks A->B->A circular reference
testRun_withContextOrderViolation_throwsValidationTask references context task appearing later
testRun_withUnusedAgent_logsWarningAgent in list but no task uses it
testRun_withValidConfig_executesSuccessfullyHappy path with mocked LLM
testRun_withInputs_resolvedTemplatesTemplate variables are substituted
testRun_calledMultipleTimes_independentEach run is independent
TestDescription
testResolve_simpleVariable{topic} -> value
testResolve_multipleVariables{a} and {b} -> values
testResolve_missingVariable_throwsAllReports ALL missing, not just first
testResolve_escapedBraces_returnsLiteral{{var}} -> {var}
testResolve_emptyValue_replacesWithEmpty{x} with value="" -> ""
testResolve_nullValue_replacesWithEmpty{x} with value=null -> ""
testResolve_noVariables_returnsUnchangedNo placeholders, returns as-is
testResolve_nullTemplate_returnsNullnull input -> null output
testResolve_blankTemplate_returnsBlank"" input -> "" output
testResolve_nullInputs_treatedAsEmptynull map treated as empty
testResolve_sameVariableMultipleTimes{a}{a} -> “XX”
testResolve_underscoreInName{under_score} is valid
testResolve_extraInputsIgnoredUnused keys in map are ignored
TestDescription
testBuildSystemPrompt_withAllFieldsRole + background + goal + responseFormat
testBuildSystemPrompt_withoutBackgroundBackground=null, omitted
testBuildSystemPrompt_withEmptyBackgroundBackground="", omitted
testBuildSystemPrompt_withoutResponseFormatresponseFormat="", omitted
testBuildSystemPrompt_minimalAgentOnly role + goal
testBuildUserPrompt_withoutContextNo context, just task + expected output
testBuildUserPrompt_withSingleContextOne prior task output
testBuildUserPrompt_withMultipleContextsMultiple prior outputs, order preserved
testBuildUserPrompt_contextOrderPreservedOutputs render in context list order
testBuildUserPrompt_emptyContextOutputContext with empty raw text, headers still shown
TestDescription
testAdapt_createsCorrectSpecToolSpecification name, description, parameters match
testAdapt_execution_delegatesToAgentToolCalls execute(), returns output
testAdapt_executionError_returnsErrorStringToolResult.failure -> “Error: …”
testAdapt_executionThrows_returnsErrorStringexecute() throws -> caught, returns error
testAdapt_nullResult_returnsEmptyexecute() returns null -> ""
TestDescription
testSuccess_withOutputsuccess=true, output set, errorMessage=null
testSuccess_withNullOutputNull coerced to empty string
testFailure_withMessagesuccess=false, errorMessage set, output=""
TestDescription
testTwoTaskEnsemble_contextPassedCorrectlyMock LLM returns “Research X”, verify second task’s prompt contains “Research X”
testSingleTaskEnsemble_executesSuccessfullyOne task, one agent, verify output
testThreeTaskChain_allContextFlowsTask 3 depends on Task 1 and 2, verify both in context
testTemplateSubstitution_variablesResolvedInPrompts{topic} resolved before execution, verify LLM receives resolved text
testEnsembleOutput_containsAllTaskOutputsVerify EnsembleOutput has correct taskOutputs list
testEnsembleOutput_rawIsLastTaskOutputraw field equals the final task’s output
testEnsembleOutput_durationsArePositiveDuration fields are > 0
TestDescription
testAgentWithTool_toolCalledAndResultUsedLLM calls tool, tool returns result, LLM uses result in final answer
testAgentWithMultipleToolCalls_allExecutedLLM calls tool A then tool B then produces answer
testAgentWithToolError_errorFedBackToLlmTool throws, error message sent to LLM, LLM produces answer
testAgentWithToolFailureResult_errorFedBackTool returns ToolResult.failure(), error sent to LLM
testMaxIterations_sendsStopMessagesLLM always calls tools, verify stop messages sent
testMaxIterations_throwsAfterThreeStopMessagesAfter 3 stops, MaxIterationsExceededException thrown
testNoTools_directLlmCallAgent with no tools, verify single generate() call
testMixedTools_bothTypesWorkAgent with AgentTool + @Tool annotated, both called
TestDescription
testLlmThrows_wrappedInTaskExecutionExceptionLLM throws RuntimeException, wrapped properly
testSecondTaskFails_firstOutputPreservedTwo tasks, second fails, first output in exception
testMissingTemplateVariable_throwsBeforeExecutionPromptTemplateException, LLM never called
testValidationFailure_noTasksExecutedValidationException, no LLM interaction
testTaskExecutionException_hasCorrectContextException fields (taskDescription, agentRole, completedOutputs) are correct

Tests are run via Gradle:

Terminal window
# Run all tests
./gradlew test
# Run specific test class
./gradlew test --tests "net.agentensemble.AgentTest"
# Run specific test method
./gradlew test --tests "net.agentensemble.AgentTest.testBuild_withNullRole_throwsValidation"
# Run integration tests only
./gradlew test --tests "net.agentensemble.integration.*"
  • All public methods of domain objects: 100% branch coverage
  • All validation paths: tested with both valid and invalid inputs
  • All exception types: at least one test that triggers each
  • Template resolver: all edge cases from the matrix in 07-template-resolver.md
  • Prompt builder: all conditional sections (with/without background, context, etc.)
  • Agent executor: tool loop, no-tool path, error handling, max iterations
  • Workflow executor: context passing, error propagation, output assembly