A month ago I found a blog post which argues that we shouldn't build the request body with ObjectMapper
when we are writing integration tests with MockMvc (aka Spring MVC Test framework). Because I think that the original blog post is quite interesting, I decided to write a blog post that evaluates if we should get rid of ObjectMapper
.
After we have finished this blog post, we:
- Understand what's the role of
ObjectMapper
. - Can identify the pros and cons of not using
ObjectMapper
. - Know if we should get rid of
ObjectMapper
.
The Role of ObjectMapper
Before we can understand the pros and cons of the solutions which I introduced in the first and second part of this article series, we have to take a closer look at the role of ObjectMapper
. We can use an ObjectMapper
object for two purposes:
- It can serialize an object into a JSON document. If we write a test which generates a request body with
ObjectMapper
, our test uses it for this purpose. Also, the system under test usesObjectMapper
for this purpose when it generates the JSON document that's added to the body of the returned HTTP response. - It can deserialize a JSON document into an object. The system under test uses
ObjectMapper
for this purpose when it transforms the request body into a data transfer object.
The following figure illustrates the role of ObjectMapper
:
When we write tests which use ObjectMapper
, we can use one of these three configurations:
1. Test classes and the system under test use the same ObjectMapper
object.
If we use this configuration, the same ObjectMapper
object that serializes an object into a JSON document deserializes the JSON document into a data transfer object that's processed by the system under test. We will use this configuration if:
- We configure MockMVC (aka Spring MVC Test framework) by using the application context based configuration, and we inject an
ObjectMapper
bean into our test class and use thisObjectMapper
object for creating the JSON documents which are send to the system under test. - We configure MockMVC (aka Spring MVC Test framework) by using the standalone configuration and:
- We configure a custom
HttpMessageConverter
which can read and write JSON by using Jackson (aka theMappingJackson2HttpMessageConverter
) and - The created
MappingJackson2HttpMessageConverter
uses the sameObjectMapper
object that's used to generate the JSON documents which are send to the system under test.
- We configure a custom
The following figure illustrates the responsibilities of the shared ObjectMapper
object:
2. Test classes and the system under test use different ObjectMapper
objects which share the same configuration.
If we use this configuration, serialization and deserialization is done by different ObjectMapper
objects, but these objects behave the same way because they use the same configuration. We will use this configuration if we have one method that creates and configures a new ObjectMapper
object, and the system under test and our tests use this method. The following figure illustrates the responsibilities of these ObjectMapper
objects:
3. Test classes and the system under test use different ObjectMapper
objects which don't share any configuration.
If we use this configuration, serialization and deserialization is done by different ObjectMapper
objects which might or might not behave the same way. We will use this configuration if the ObjectMapper
objects used by the system under test and our tests aren't created or configured by the same code. The following figure illustrates the responsibilities of these ObjectMapper
objects:
We should now understand how we can use ObjectMapper
when we are writing tests with MockMVC (aka Spring MVC Test framework). Next, we will take a look at the pros and cons of not using the ObjectMapper
.
The Pros and Cons of Not Using ObjectMapper
Before we can decide if we should use ObjectMapper
when we build the request body that's send to the system under test, we must identify the pros and cons of alternative solutions. This section helps us to do just that.
The pros of not using ObjectMapper
are:
1. We have full control over the request body that's send to the system under test. If we build the request body with ObjectMapper
, we cannot control:
- The structure of the JSON document because that's specified by the data transfer object which is passed to the
ObjectMapper
. - The formats which are used by the property values because the serialization behavior of an
ObjectMapper
object depends on its configuration.
This means that we cannot write tests which ensure that the system under test is working as expected when:
- The request body is missing a property.
- The request body contains unknown properties.
- The property values use unsupported format. For example, we cannot write tests which use unsupported date or timestamp formats.
On the other hand, if we don't use ObjectMapper
when we build the request body that's send to the system under test, we don't have these problems because we have full control over the structure of the JSON document and the formats used by the property values.
ObjectMapper
.
If we want to write tests which ensure that the system under test is working as expected when the request body is missing a property, we have create a duplicate DTO class which contains the same properties as the "original" class and doesn't contain the missing property. After we have created a new DTO class, we have to use the created class when we want to create the request body with ObjectMapper
. However, I wouldn't use this technique because it makes our tests hard to maintain.
If we want to write tests which ensure that the system under test is working as expected when the request body contains unknown properties, we can either:
- Create a duplicate DTO class which contains all properties of the "original" class and all unknown properties.
- Create a new DTO class which extends the "original" DTO class and declares the required unknown properties.
After we have created a new DTO class, we have to use the created class when we want to create the request body with ObjectMapper
. However, I wouldn't use this technique because the first option makes our tests hard to maintain and the second options adds unnecessary complexity to our test suite.
If we want to write tests which ensure that the system under test is working as expected when the property values use unsupported format, we have to create a new ObjectMapper
object and ensure that it uses the unsupported format when it serializes the data transfer object. Even though this works, the downside of this approach is that managing the different ObjectMapper
configurations adds unnecessary complexity to our test suite.
2. It's possible/easier to write tests that can fail. If we build the request body with ObjectMapper
and we configure the ObjectMapper
by using either the option one or two, we cannot write tests which can fail because the serialization and deserialization behavior depends on the configuration of the ObjectMapper
, and the system under test and our tests use the same configuration.
On the other hand, if we don't create the request body with ObjectMapper
, it's easier to write tests which can fail because we have full control over the request body that's send to the system under test. For example, if we change the supported date or timestamp format, the tests which use the old format will fail.
ObjectMapper
by using the option three, we can write tests which can fail because the ObjectMapper
objects used by the system under test and our tests aren't created or configured by the same code. In other words, if we change the configuration of the ObjectMapper
that's used by the system under test, the behavior of the ObjectMapper
that's used by our tests won't change and our tests will fail.3. It's easy to see what kind of a JSON document is send to the system under test. If we build the request body with ObjectMapper
and we want to take a look at the JSON document that's send to the system under test, we have to run our test and use a debugger or invoke the print()
method of the MockMvcResultHandlers
class. Although these options aren't super complicated, it's a lot easier to simply take a look at the string that contains the request body or take a look at the template that's used to build the actual request body.
print()
method of the MockMvcResultHandlers
class. That being said, I have noticed that seeing the request body template is often extremely helpful.4. We can write more comprehensive tests for the deserialization logic. If we don't build the request body with ObjectMapper
, our tests might catch deserialization bugs which don't occur if we build the request body with ObjectMapper
. Also, philosophically speaking, I think that our tests are "better" because the input data (aka JSON document) isn't produced by the same library which consumes it.
The cons of not using ObjectMapper
are:
1. We might write code that's hard to write and maintain. If we specify the required request bodies by using static strings, we often have multiple different versions of the same JSON document. In other words, we have to declare one constant per version. This makes our tests hard to write. Also, if we have to make changes to our test data, we have to make the required changes to multiple constants. This means that our tests tests are hard to maintain.
2. We might have to write code that isn't easy to read and write. If we specify the required request bodies by using string concatenation, our test code looks awful and is hard to write. On the other hand, Because the format()
method of the String
class doesn't support named arguments, it's easy to make a programming error. In other words, because we have to be extra careful and ensure that our code builds the "correct" request body, using this method is somewhat slow.
3. We might have to write more custom code. If we build the required request bodies by using a template engine, we have to write custom code that must be maintained in the future. This will take more time than maintaining the test code which builds the required request bodies with ObjectMapper
.
We are now familiar with the pros and cons of not using ObjectMapper
. Let's move on and find out if we should get rid of ObjectMapper
.
Should We Get Rid of ObjectMapper?
I would love to say: absolutely because controversy would most likely bring me a lot of readers, but the truth is that it depends. When we make this decision, we should take the following things into account:
1. What's our risk tolerance level? If our risk tolerance level is low, we have to try to eliminate risks which we can ignore if our risk tolerance level is high. In other words, if our risk tolerance level is low, we have to write more (and better) automated tests. If we are in this situation, it makes sense to replace ObjectMapper
with a tool that helps us to write more comprehensive tests.
2. How many tests do we already have? If we have a lot of tests which generate the input data with ObjectMapper
, we might not be able to get rid of ObjectMapper
because we don't have time to refactor our existing tests. There are situations when being consistent is more important than using the best approach.
3. Should we find a compromise? I think that one reason why people want to use ObjectMapper
for generating the input data is that it's so easy. That's is why it can be hard to convince these people to adopt a new technique that requires more work (from their point of view). Nevertheless, if we want to write tests which require input data that cannot be generated with ObjectMapper
, it could be wise to find a compromise where:
- Most of the tests would use
ObjectMapper
for building the input data that's send to the system under test. - The tests which cannot use
ObjectMapper
would use something else like a static string, a dynamic string, or a template engine.
4. What's our preference? Everyone of us has our own preferences (sometimes they are called unconscious biases) which will cloud our judgment unless we are aware of them and take them into account when we make decisions like this. For example, if I start to feel uncomfortable or maybe even a bit angry when I am reading an article or discussing about something, I know that the article or the other person is making a good point and I am reacting to it because I want to stay on my comfort zone and it's not possible if I admit that I am wrong. In other words, if the idea of getting rid of ObjectMapper
makes you feel uncomfortable, you should try to figure out why you react in this way (and perhaps get rid of ObjectMapper
).
We can now identify the things which we should take into account when we decide if we should ditch ObjectMapper
. Let's summarize what we learned from this blog post.
Summary
This blog post has taught us seven things:
- An
ObjectMapper
object can serialize an object into a JSON document and deserialize a JSON document into an object. - If we don't use
ObjectMapper
, we have full control over the request body that's send to the system under test. This gives us the possibility to write more comprehensive tests that can fail. - If we replace
ObjectMapper
with a static string, our tests will be hard to write and maintain. - If we replace
ObjectMapper
with a dynamic string, our tests will be hard to read and write. - If we replace
ObjectMapper
with a template engine, we have to write custom code that must be maintained in the future. - If our risk tolerance level is low, we shouldn't use
ObjectMapper
when we generate the JSON document that's send to the system under test. - Sometimes it's wise to find a compromise. In other words, we can use
ObjectMapper
as long as we are ready to use something else (a static string, a dynamic string, or a template engine) when we have to write tests which require input data that cannot be generated withObjectMapper
.