Java Fixture API

The SaplTestFixture class provides a programmatic API for testing SAPL policies from Java. It uses a fluent Given-When-Then builder pattern. Use this API when the test DSL does not cover your use case, for example when you need custom assertion predicates, specific timing control, or integration with Java test infrastructure.

Creating a Test Fixture

The fixture API has two entry points. createSingleTest() creates a unit test that evaluates a single policy document. createIntegrationTest() loads multiple documents and combines their decisions through a combining algorithm. Both follow the same fluent builder chain: load policies, set up mocks, submit a subscription, assert the decision.

// Unit test (single document)
var result = SaplTestFixture.createSingleTest()
    .withPolicyFromResource("policies/patient-access.sapl")
    .givenFunction("time.dayOfWeek", args(any()), Value.of("MONDAY"))
    .whenDecide(AuthorizationSubscription.of("Dr. Smith", "read", "patient_record"))
    .expectPermit()
    .verify();

// Integration test (multiple documents)
var result = SaplTestFixture.createIntegrationTest()
    .withConfigurationFromResources("policiesIT")
    .withCombiningAlgorithm(CombiningAlgorithm.of(PRIORITY_DENY, DENY, ABSTAIN))
    .whenDecide(AuthorizationSubscription.of("user", "read", "resource"))
    .expectPermit()
    .verify();

Loading Policies

Unit tests load a single document by path or inline source. Integration tests load a full configuration directory containing multiple .sapl files and a pdp.json.

Method Description
withPolicyFromResource(path) Load from classpath resource
withPolicyFromFile(path) Load from filesystem
withPolicy(source) Inline policy source
withConfigurationFromResources(path) Load all policies and pdp.json from classpath directory
withConfigurationFromDirectory(path) Load all policies and pdp.json from filesystem directory

Mocking

The fixture provides the same mocking capabilities as the test DSL. Functions are mocked with argument matchers from io.sapl.test.Matchers. Attribute mocks are identified by a mock ID string and an attribute name. Variables and secrets configure PDP-level state.

import static io.sapl.test.Matchers.*;

// Function mock
.givenFunction("time.dayOfWeek", args(any()), Value.of("MONDAY"))

// Environment attribute mock with initial value
.givenEnvironmentAttribute("timeMock", "time.now", args(), Value.of("2026-01-15T10:00:00Z"))

// Entity attribute mock
.givenAttribute("upperMock", "string.upper", any(), args(), Value.of("HELLO"))

// PDP variables and secrets
.givenVariable("tenant", Value.of("hospital-north"))
.givenSecret("api_key", Value.of("sk-test-key"))

Decision Expectations

Simple expectations check only the decision type. For policies that attach obligations, advice, or a transformed resource, use expectDecisionMatches() with a decision matcher. Custom predicates give full control for assertions that the built-in matchers do not cover.

// Simple decisions
.expectPermit()
.expectDeny()
.expectIndeterminate()
.expectNotApplicable()

// Decision matcher with obligations and resource
.expectDecisionMatches(isPermit()
    .containsObligation(Value.of(Map.of("type", "logAccess")))
    .withResource(expectedResource))

// Custom predicate
.expectDecisionMatches(isPermit()
    .containsObligationMatching(obligation ->
        obligation instanceof ObjectValue obj
        && obj.get("type") instanceof TextValue(var type)
        && "audit".equals(type)))

Streaming

Streaming tests emit new values to attribute mocks between expectations, just like then blocks in the DSL. The thenEmit() method takes the mock ID and the new value. The fixture waits for the PDP to re-evaluate and checks the next expectation.

.givenEnvironmentAttribute("timeMock", "time.now", args(), Value.of("morning"))
.whenDecide(subscription)
.expectPermit()
.thenEmit("timeMock", Value.of("night"))
.expectDeny()
.verify();

Registering Libraries and PIPs

When your policies use custom function libraries or PIPs, register them with the fixture. Function libraries are registered by class (the fixture instantiates them). PIPs are registered as instances, which allows injecting dependencies like service clients.

// Static function library
.withFunctionLibrary(TemporalFunctionLibrary.class)

// All default function libraries
.withDefaultFunctionLibraries()

// PIP instance
.withPolicyInformationPoint(new UserPIP(userService))

Execution

The verify() method executes the test with a default timeout of 10 seconds:

TestResult result = fixture.verify();

A custom timeout can be specified:

TestResult result = fixture.verify(Duration.ofSeconds(30));