Home > Enterprise >  How to make order-independent assertions on Flux output?
How to make order-independent assertions on Flux output?

Time:05-06

I have a test case for a Flux from Project Reactor roughly like this:

testMultipleChunks(StepVerifier.FirstStep<Chunk> verifier, Chunk chunk1, Chunk chunk2) {
    verifier.then(() -> {
        worker.add(chunk1);
        worker.add(chunk2);
        worker.update();
    })
    .expectNext(chunk1, chunk2)
    .verifyTimeout(Duration.ofSeconds(5));
}

Thing is, my worker is encouraged to parallelize the work, which means the order of the output is undefined. chunk2, chunk1 would be equally valid.

How can I make assertions on the output in an order-independent way?

Properties I care about:

  • every element in the expected set is present
  • there are no unexpected elements
  • there are no extra (duplicate) events

I tried this:

testMultipleChunks(StepVerifier.FirstStep<Chunk> verifier, Chunk chunk1, Chunk chunk2) {
    Set<Chunk> expectedOutput = Set.of(chunk1, chunk2);
    verifier.then(() -> {
        worker.add(chunk1);
        worker.add(chunk2);
        worker.update();
    })
   .recordWith(HashSet::new)
   .expectNextCount(expectedOutput.size())
   .expectRecordedMatches(expectedOutput::equals)
   .verifyTimeout(Duration.ofSeconds(5));
}

While I think that makes the assertions I want, it took a terrible dive in readability. A clear one-line, one-method assertion was replaced with four lines with a lot of extra punctuation.

expectedRecordedMatches is also horribly uninformative when it fails, saying only “expected collection predicate match” without giving any information about what the expectation is or how close the result was.

What's a clearer way to write this test?

CodePudding user response:

StepVerifier is not a good fit for that because it verifies each signal as it gets emitted, and materialize an expected order for asynchronous signals, by design.

It is especially tricky because (it seems) your publisher under test doesn't clearly complete.

If it was completing after N elements (N being the expected amount here), I'd change the publisher passed to StepVerifier.create from flux to flux.collectList(). That way, you get a List view of the onNext and you can assert the list as you see fit (eg. using AssertJ, which I recommend).

One alternative in recent versions of Reactor is the TestSubscriber, which can be used to drive request and cancel() without any particular opinion on blocking or on when to perform the assertions. Instead, it internally stores the events it sees (onNext go into a List, onComplete and onError are stored as a terminal Signal...) and you can access these for arbitrary assertions.

  • Related