How to properly test event upcasting?

Hi,

We recently had to create new versions of certain events. As such we also had to write an event upcaster. Now we were wondering how to create tests for upcasters.

We have been experimenting with creating a GenericMessage with the payload of a version 1.0 event and delivering this to the aggregate, but it doesn’t seem possible to attach the upcaster to the FixtureConfiguration of our unit tests.
Perhaps this is not so strange since the upcaster seems to be tightly coupled with database access when looking at the required spring boot configuration on https://docs.axonframework.org/part3/repositories-and-event-stores.html
But what other options does that leave us?

Regards,
Jens

The approach we take is to have a JUnit test that does a classpath scan looking for event classes (our naming convention is to give them all names ending in “Event”). For each event class, it expects to see a directory with one or more XML files corresponding to the various revisions of the event. It attempts to deserialize each one including running it through the upcaster chain. Another test method, disabled by default, writes these files out; when we add a new event or revise an existing one, we run that method and commit the serialized versions of the objects to version control as test resources.

This works pretty well and ensures we never deploy code that can’t deserialize old versions of any of our events. The ugliest thing about it is that the test class needs to build a big collection of example instances of all the event classes so there’s something sensible to test against. This ends up looking like a giant block of hundreds of lines like

examples.add(new CancellationFailedTooLateEvent(transactionId));
examples.add(new CancellationRequestedEvent(transactionId));
examples.add(new CannotReverseReversedTransactionEvent(transactionId, reversalId));
examples.add(new CannotStopReversingReversedTransactionEvent(transactionId, reversalId));

Our concern is mostly structural correctness (basically: does deserializing an old event object bomb out) but it’d be easy to feed the upcasted objects into a validator to make sure the upcasting did its transformations correctly.

-Steve

Hi Jens, Steven, here’s my two cents.

I’ve worked on a similar solution Steven described, although it didn’t automatically grab the XML files for verification, but we had a test class for each event update.

So, lets take it we have ExampleEvent version 1, which we update, so we get an ExampleEvent version 2.
We thus require an upcaster from version 1 to 2, ExampleEventUpcaster1_2.
We’d then have a test for the ExampleEventUpcaster1_2 which grabs a deserialized version of ExampleEvent 1, upcasts it, and asserts it compared to a deserialized version of ExampleEvent 2.
For an upcaster from 2 to 3 we’d rinse and repeat this process.

Having an automation process like Steven Grimm suggests is nice I think though, might use that two soon.

Hope this gives some insights.

Cheers,

Steven

Hi,

We’ve also developed a more generic solution based on the ideas discussed here.
If still of your interest, you can read about it here.

Regards,
Johan.

Hi Johan,

I like you proposed solution and it is quite similar to ours.

What we do is quite the same: We add an interface to define an upcasting process for a single (JSON-serialization of an) event (qualified name), a single source revision, and a single target revision. Implementations of those interface are wired to build the whole EventUpcasterChain, meaning that the Upcaster internals like the “IntermediateRepresentations” are not visible in our interface. Testing can be done as follows:

  • We unit test the implementation of our interface by adding (copying) the source revision of the target event as a new private class to test file. Then we serialize the copied event, run the implementation of our interface, and deserialize to the new target revision, again.

  • We try to unit test the overall Upcaster that gathers all the above mentioned interface implementations. But this is not easiliy possible as the methods are protected. After changing it to public (which seems wrong) we were able to unit test with mocked IntermediateRepresentations. This is not ideal, but at least it works. The main idea is that we do only want to test the overriden methods of the Upcaster interface but not the internals of Axon.

What we also lack (or at least hoped to find) was a solution to test the Upcaster easily, e.g. using a fixture in an aggregate test. It would be nice to be able to inject an event to the Datastore at the beginning of a test run with a given (custom) qualified class name, revision, and a custom payload. The main problem is that there can always only be a single class with the given qualified name and thus only changing the revision (which is what we expect to be sufficient for upcasting) does not work for tests. I know that a workaround like adding the revision to the event class name solves this issue but this is not the way I would like to see Axon working. (Then the revisions are more or less useless.) It would be really great to make Upcaster testing easier and hopefully, such a feature will be added in the future.

Best regards,
Markus