Skip to content

Implement shared specifications tests#6

Merged
liamhughes merged 16 commits intomainfrom
liamhughes/devex-138-implement-specifications-tests-in-java-client
Mar 27, 2026
Merged

Implement shared specifications tests#6
liamhughes merged 16 commits intomainfrom
liamhughes/devex-138-implement-specifications-tests-in-java-client

Conversation

@liamhughes
Copy link
Copy Markdown
Contributor

@liamhughes liamhughes commented Mar 27, 2026

Background

We are adding shared specification tests across our three OpenFeature provider libraries as per this ADR.

The one existing test fixture is imported from our specifications repository as a Git submodule. Our intention is to add a separate fixture file for each area of functionality.

Each test fixture set will contain a list of toggles and test cases with expected outcomes.

  • The toggles list will take the same shape as the response body returned by the OctoToggle API.
  • The test cases will each contain a description, toggle slug, evaluation context, and expected outcome including any expected error code.

Changes

  • Modified OctopusConfiguration to have a package-private settable server URI.
  • Added Server, a wrapper around a WireMock server allowing registration of evaluation payloads for particular client identifiers.
  • Added SpecificationTests which uses @ParameterizedTest and @MethodSource to run a test for each case in each specification JSON file using the WireMock server as the service for the OpenFeature client to query.

Result

Screenshot 2026-03-27 at 14 39 59

@liamhughes liamhughes force-pushed the liamhughes/devex-138-implement-specifications-tests-in-java-client branch from 8eb6720 to db7548c Compare March 27, 2026 03:36
@liamhughes liamhughes changed the title Liamhughes/devex 138 implement specifications tests in java client Implement specifications tests Mar 27, 2026
@liamhughes liamhughes changed the title Implement specifications tests Implement shared specifications tests Mar 27, 2026
);

assertThat(result.getValue())
.as("[%s] %s → value", fileName, description)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not 100% sure on the necessity of this formatting.

public String errorCode;
}

static class RawJsonDeserializer extends JsonDeserializer<String> {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Jackson does not have the equivalent of GetRawText. I could have fetched a generic JSON object and then reserialised it, but I'd prefer to get the raw text from the file to avoid unintentional changes to the "response".


static Stream<Arguments> fixtureTestCases() throws IOException {
ObjectMapper mapper = JsonMapper.builder()
.enable(StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Required for the RawJsonDeserializer below.

@liamhughes liamhughes requested a review from Copilot March 27, 2026 04:07
@liamhughes liamhughes marked this pull request as ready for review March 27, 2026 04:13
@liamhughes liamhughes requested a review from a team March 27, 2026 04:13
Base automatically changed from liamhughes/devex-80-java-client-breaks-on-segments-and-new-response-fields to main March 27, 2026 04:37
@liamhughes liamhughes force-pushed the liamhughes/devex-138-implement-specifications-tests-in-java-client branch from e1c6d2f to 91bab70 Compare March 27, 2026 04:40
@liamhughes liamhughes merged commit 51d5af0 into main Mar 27, 2026
5 checks passed
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a shared, submodule-backed specification test harness to validate OpenFeature provider behavior against JSON fixtures.

Changes:

  • Introduces parameterized SpecificationTests that discover and run JSON fixture cases against a WireMock-backed fake service.
  • Adds a Server helper wrapping WireMock for per-test-case Bearer-token-based stubbing.
  • Updates configuration/CI to support the new test approach (package-private server URI override, submodule checkout).

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/test/java/com/octopus/openfeature/provider/SpecificationTests.java New parameterized spec runner that loads fixture JSON and asserts evaluation results.
src/test/java/com/octopus/openfeature/provider/Server.java WireMock wrapper to serve fixture responses keyed by unique tokens.
src/test/java/com/octopus/openfeature/provider/OctopusConfigurationTests.java Tests for default server URI and overriding server URI.
src/main/java/com/octopus/openfeature/provider/OctopusConfiguration.java Makes server URI configurable (package-private setter) for tests.
specification Adds the specification repo as a git submodule pointer.
pom.xml Adds WireMock test dependency and scopes JUnit to tests.
.github/workflows/ci.yml Ensures submodules are checked out in CI and updates workflow naming/steps.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +93 to +100
return jsonFiles.stream().flatMap(path -> {
try {
String fileContent = Files.readString(path);
Fixture fixture = mapper.readValue(fileContent, Fixture.class);
String fileName = path.getFileName().toString();
return Stream.of(fixture.cases)
.map(c -> Arguments.of(fileName, c.description, fixture.response, c));
} catch (IOException e) {
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stream.of(fixture.cases) creates a Stream<FixtureCase[]> (single element: the array), so c.description won’t compile/work. Use an element stream (e.g., Arrays.stream(fixture.cases)) so each FixtureCase becomes a separate Arguments row.

Copilot uses AI. Check for mistakes.
Comment on lines +161 to +170
static class RawJsonDeserializer extends JsonDeserializer<String> {
@Override
public String deserialize(JsonParser jp, DeserializationContext dc) throws IOException {
long begin = jp.currentLocation().getCharOffset();
jp.skipChildren();
long end = jp.currentLocation().getCharOffset();
String json = jp.currentLocation().contentReference().getRawContent().toString();
return json.substring((int) begin - 1, (int) end);
}
}
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This “slice raw source by char offsets” approach is fragile (offset semantics vary by parser/source, begin - 1 can underflow, getRawContent().toString() is not guaranteed to be the actual JSON text, and (int) casts can truncate). A more robust approach is to deserialize response as a JsonNode (or Object) and then serialize it when configuring the server (e.g., mapper.writeValueAsString(node)), avoiding any dependency on raw input offsets.

Copilot uses AI. Check for mistakes.
Comment on lines +31 to +48
class SpecificationTests {

private static Server server;

@BeforeAll
static void startServer() {
server = new Server();
}

@AfterAll
static void stopServer() {
server.stop();
}

@AfterEach
void shutdownApi() {
OpenFeatureAPI.getInstance().shutdown();
}
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests share global mutable state (OpenFeatureAPI singleton and a static Server with accumulating stubs). If JUnit parallel execution is enabled (locally or in CI), this can cause cross-test interference/flakiness. Consider explicitly forcing same-thread execution for this class (e.g., JUnit 5 @Execution(ExecutionMode.SAME_THREAD)), or otherwise applying a lock/serialization mechanism around these tests.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants