question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Testing command, event and aggregate (de)serialization

See original GitHub issue

Given the discussion here I was asked to create an issue here in order to discuss testing (de)serialization of commands, events and snapshots.

I’ve created a small sample project in which I show how we currently at Orangebeard test (de)serialization of events and how we test our upcasters: https://github.com/maartenjanvangool/axon-event-testing

Testing the deserialization can be done like this (this is kotlin):

    // should be the serializer used in production
    private val serializer = XStreamSerializer.builder().xStream(XStream()).build()

    @Test
    fun can_serialize_and_deserialize() {
        val serializedObject = serializer.serialize(event, String::class.java)
        val result = serializer.deserialize<String, FlightCreatedEvent>(serializedObject)

        assertXMLEquals(serializedObject.data, xml)
        assertThat(event).isEqualTo(result)
    }

    private val event: FlightCreatedEvent = FlightCreatedEvent(
        1L,
        LocalDateTime.parse("2019-10-08T12:42:23.329")
    )

    //language=xml
    private val xml = """
        <io.orangebeard.events.FlightCreatedEvent>
            <flightId>1</flightId>
            <departureTime>2019-10-08T12:42:23.329</departureTime>
        </io.orangebeard.events.FlightCreatedEvent>"""

And testing the working of an upcaster like this:

    // should be the serializer used in production
    private val serializer = XStreamSerializer.builder().xStream(XStream()).build()

    private val v1Upcaster = FlightDelayedEventV1Upcaster()

    @Test
    fun can_upcast() {
        val event = InitialEventRepresentation(
            GenericDomainEventEntry(
            "io.orangebeard.events.FlightDelayedEvent",
            "12045560",
            1L,
            "a904422b-6ae0-4123-a08a-e1e0cfd0f7bd",
            "2019-10-17T11:58:20.688Z",
                "io.orangebeard.events.FlightDelayedEvent",
            "1",
            xmlV1,
            ""), serializer)

        assertThat(v1Upcaster.canUpcast(event)).isTrue()

        val result = v1Upcaster.doUpcast(event)

        assertThat(result.data.type.revision).isEqualTo("2")
        assertXMLEquals(xmlV2, (result.data.data as AbstractDocument).asXML())
    }

    //language=xml
    private val xmlV1 = """
        <io.orangebeard.events.FlightCreatedEvent>
            <flightId>1</flightId>
            <departureTime>2019-10-08T12:42:23.329</departureTime>
            <reason>SECURITY_PROBLEM</reason>
        </io.orangebeard.events.FlightCreatedEvent>"""

    //language=xml
    private val xmlV2 = """
        <io.orangebeard.events.FlightCreatedEvent>
            <flightId>1</flightId>
            <departureTime>2019-10-08T12:42:23.329</departureTime>
            <reason>SECURITY_ISSUE</reason>
        </io.orangebeard.events.FlightCreatedEvent>"""

The issue I encountered was that there does not seem a good way to test deseralization of aggregate snapshots. Since events and commands are simple pojo’s, one can just instantiate these and test the deserialization. This is not possible with aggregates. And changing the aggregate while not changing the snapshot revision number can have some dire consequences.

So I essentially propose an extension to the test fixture where I can pass along the serializer used, as well as snapshots in order tot test the interaction between snapshot and aggregate (changes), which may look something like this:

    @BeforeEach
    public void setUp() {
        // pass serializer as an argument (or as a bean)
        fixture = new AggregateTestFixture<>(FlightAggregate.class, serializer); 
    }
    
    @Test
    public void the_aggregate_snapshot_serializes_correctly() {
        fixture.givenNoPriorActivity()
          .when(new FlightCreatedEvent(1L, now))
          .expectSnapshotToMatch(snapshotXML);
    }

    @Test
    public void the_aggregate_snapshot_deserializes_correctly() {
         fixture.givenSnapshot(snapshot)
                .when(new FlightCreatedEvent(1L, now))
              ...
    }

What do you guys think?

Issue Analytics

  • State:open
  • Created 2 years ago
  • Reactions:2
  • Comments:5 (4 by maintainers)

github_iconTop GitHub Comments

2reactions
smcvbcommented, May 2, 2022

Shouldn’t integration tests cover this implicitly? Am I looking over something why this wouldn’t be tested by integration tests? We just had a discussion about this in the team.

I wouldn’t indeed assume they would, @TomDeBacker. The times I did help teams integrate with Axon Framework, we always reached a point where we introduced a test suite in the staging environment that validated all events and snapshots with the Serializer and Upcasters in place. However, this was always purpose-built, whereas a form of support from within the framework does sound like a friendly gesture to me.

So, your team is right! But that does not necessarily mitigate this issue at this stage I believe. 😃

2reactions
TomDeBackercommented, May 1, 2022

I’m looking through some of the issues to provide some feedback. This is one of the things we do by default in our team. As stated, it is probably not a big priority but seems to be something that could reduce developer overhead, even if minimal. You will have to take the specific event serializer used into account in any case. +1 from me

Read more comments on GitHub >

github_iconTop Results From Across the Web

Advanced customizations - Axon Reference Guide
The Event Serializer is in charge of de-/serializing Event messages. Events are typically stored in an Event Store for a long period of...
Read more >
Testing Aggregates - EventSauce
Event sourced applications are very easy to test. EventSauce ships with test tooling that allows for scenario based testing using a given/when/then structure....
Read more >
Axon Event Deserialization Issue? - Stack Overflow
I have an eventsourced aggregate and use Jackson as eventserializer. Now, when I apply an event A in a command handler, I can...
Read more >
commanded/Lobby - Gitter
What's the easiest way to encode and decode events in the aggregate case. ... serialization/deserialization issues will be hit in integration tests (for...
Read more >
Systematically Hunting for Deserialization Exploits - Mandiant
NET ViewState and Java deserialization exploits to target companies ... Since we want to test Base64 rules, we can also add a command...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found