DEV Community

Cover image for Testing your JSON API in Ruby with dry-rb
Paul Götze
Paul Götze

Posted on • Originally published at Medium

Testing your JSON API in Ruby with dry-rb

When writing a JSON API with Ruby in your favorite web framework, you will certainly come to a point, where you have to decide how to test your endpoints. In this article I’m going to show one of my preferred ways to test the structure of JSON responses in a clear and readable way—by making use of the dry-schema gem and some other dry-rb libraries.


There’s Different Ways

When it comes to testing your JSON API endpoints in Ruby, you can go for plenty of different approaches. The simplest probably is to check the status code and explicitly assert each value of you response data.

Let’s assume we want to test a GET /todos endpoint, which returns to-do items like this:

Then a simple test for this response could look like this:

However, this approach is a bit cumbersome for large response bodies and can result in a lot of accesses to nested hash keys, until you get to the value you actually want to check. It will also take you quite some effort to adjust the tests as soon as some keys or values in the JSON response change.

So, instead of checking for each exact value, you could move up one level of abstraction and only check for the right structure and data types in your response body. Again there’s different ways to do that.

You could use JSON Schema files and validate your response against them, as is described here:

Validating JSON Schemas with an RSpec Matcher

Use RSpec and JSON Schema to create a test-driven process in which changes to the structure of your JSON API drive the implementation of new features.

favicon thoughtbot.com

The JSON Schema approach works quite well. However, writing JSON Schema files can get a bit tricky and confusing, especially when you have to deal with complex and nested JSON data. There’s ways to handle this complexity, e.g. by splitting up and reusing schemas by referencing them.

But maybe there’s a more legible and maintainable way than JSON Schema validation. Let’s explore the realm of dry-rb for this purpose.

What is dry-rb?

dry-rb is a collection of Ruby libraries, whose goal is to encapsulate common tasks–like data validation, data transformation, Ruby object initialization, and others–in small, reusable, and framework-independent Ruby gems.

For our purpose of validating the structure and types of JSON data it provides us with the dry-validation, dry-schema, and dry-types gems. These gems give us all we need for setting up a schema to validate JSON data of almost any complexity.

So let’s have a look at some validation use cases and let’s see how we can leverage the mentioned dry-rb gems for a simple JSON structure. We will also dig into a more complex one afterwards.

A Simple JSON Response

The dry-schema gem comes with a Dry::Schema module which has a Params method to define schemas for validating hashes. Built on top, it also has a JSON method for defining JSON schemas with coercible data types, like Date or Time. This is the one we want to use for our response validation.

A schema for one of our to-do items from above would look something like this:

With these definitions we make sure that an id, title, and the done flag is present in our to-do item JSON object–and that each property has the given type.

Instead of required() we can also use optional(), to allow the object to not have this property. Instead of value() we can also put maybe() to allow nil values.

Having this schema defined, we can now run the validation in our test for each to-do item by calling the schema, instead of checking for each value explicitly as we did before:

Dry-schema also allows us to nest schemas and to pass schemas as property types. So, let’s build a schema for our entire JSON response:

Putting all of this together in our test case we can now validate our response body to match the ResponseSchema:

Of course you might still want to check some of the JSON data’s properties explicitly, e.g. to make sure the right to-do ids are involved.

Nevertheless, using dry-schemas to validate the response structure makes your test code a bit more concise already and also makes it easier to understand what the actual JSON response looks like. You can have a quick look at the involved schemas to get an idea about required or optional properties and their (maybe nullable) types.

A More Complex JSON Response

You might think: “Well, nice little examples, but my real-world JSON data is way more complex. Can I use this for these structures, too?”.

I think you can. Let’s see how.

Let’s replace some parts of our simple to-do JSON with more complex ones. Instead of the done flag we will have a done_at timestamp, we will add a created_at timestamp and a priority (which could be one of high, default, or low). We will also assume that we have a collaborative to-do list, where we want to include the creator and assignee for each to-do item. Last, let’s have an arbitrary list of tags that can be assigned for a to-do item. This is how our new JSON response might look like:

A very basic schema for this response would be something like the following:

Note that we used maybe() for the done_at property, which allows to have nil values.

This only validates the presence and the top level type of each property. The dry-types gem that is used by our schema, however, provides us with a number of built-in types, like Types::JSON::DateTime (which will be used under the hood if we type :date_time), Types::Array, and Types::String.enum. We can use these to refine our timestamps and tags types in the schema. In order to create custom types we need to create a Types module and include the dry types. Similar to what we already saw for the simple JSON response, we can also create and reuse sub-schemas for the creator and assignee properties:

This now looks already more like a real-world use case!

Still, we can go one step further and check for the integrity of certain properties. With dry-validation (which is powered by dry-schema) we have a tool at hand, that allows us to define custom rules for our JSON data on top of our existing schema definitions.

Before we can define custom rules we need to change our schema definition from the Dry::Schema.JSON block to a response class that inherits from the Dry::Validation::Contract class:

The Dry::Validation::Contract base class comes with a rule method that we can use to raise custom validation failures if certain conditions match.

Inside a rule we have access to a values variable, which holds the value(s) of the configured property for that rule. As soon as we assign a failure message (by calling key.failure("some message")) we will make the validation fail.

Let’s say we want to validate the done_at DateTime to always be after the created_at DateTime, and each tag in tags to only appear once. Then these might be our rule definitions:

You can define whatever rules you can come up with to customize your validation contract and to adjust it to your needs. Dry-rb already provides the most often used data types, but you can also use fully customized ones as we saw in the examples above.

Note that our first ResponseSchema definition, where we made use of Dry::Schema.JSON, gave us an instance of the schema class already. When using the Dry::Validation::Contract we need to create an instance of our ResponseSchema ourselves, before we can call its call() method to validate our data.

For more details and dry-validation features I encourage you to checkout the dry-validation documentation.

Some Assertion Syntactical Sugar 🍬

One issue with directly asserting if a schema validation succeeds is that you won’t see what went wrong if the validation failed. Dry-schema provides detailed error messages, so we can make use of these to debug our failing response validation.

We can fix the visibility issue of failure messages by setting up a small test helper method:

If the validation fails you will now be pointed to the erroneous properties and you will see a message about what went wrong. On top you will get the original JSON response, so you can spot the validation issue in no time.

Conclusion

We saw that dry-rb gems, specifically the dry-schema and dry-validation gems provide us with some useful tools to make JSON response tests in Ruby more legible and maintainable.

On top of making our JSON response tests more concise, the schemas we set up with this approach also allow us to have a quick overview about the structure and data types of our endpoint’s response.

Schemas can be reused in other schemas and tests. They are easy to change and framework agnostic. So, although I used minitest assertion syntax in my code examples, with some small adjustments the shown approaches can also be applied if you use RSpec or any other Ruby testing library.


I hope you enjoyed this little journey into JSON response testing with dry-rb—thanks for reading and I’m curious to hear about your experiences with using dry-rb gems or similar approaches in your testing environment.

Happy coding, happy testing!


Credits: dryer icon in the header image by photo3idea_studio from flaticon.com

Top comments (0)