DEV Community

Cover image for Recursion to simplify test assertions
Sibelius Seraphini for Woovi

Posted on

Recursion to simplify test assertions

At Woovi we use MongoDB as our primary database.
For our integration tests, we use an in memory database to ensure our queries and aggregates are working correctly.
It is madness to mock a database, it generates a lot of false positives, and it won't give confidence to upgrade.

How to assert ObjectId?

ObjectIds are small, likely unique, fast to generate, and ordered.
ObjectId is the primary key of each MongoDB document.

A common test scenario is to validate if a given ObjectId is equal to another ObjectId

In a naive approach, you use assert like this

expect(objectIdA).toBe(objectIdA);
Enter fullscreen mode Exit fullscreen mode

This does not work because ObjectId are an Object in JavaScript, and even if they have the same value, they will be in different places in the memory.

The way to solve this, it is to convert the ObjectId to string using toString() method, and then make the assertion

expect(objectIdA.toString()).toBe(objectIdA.toString());
Enter fullscreen mode Exit fullscreen mode

What if you want to assert 2 complex objects, like this one:

const obj = {
    _id: new ObjectId('5c9b1b9b9b9b9b9b9b9b9b9b'),
    name: 'test',
    myarr: [
      new ObjectId('5c9b1b9b9b9b9b9b9b9b9b9b'),
      new ObjectId('5c9b1b9b9b9b9b9b9b9b9b9b'),
      new ObjectId('5c9b1b9b9b9b9b9b9b9b9b9b'),
    ],
    my: {
      nested: {
        field: new ObjectId('5c9b1b9b9b9b9b9b9b9b9b9b'),
      },
    },
  };
Enter fullscreen mode Exit fullscreen mode

Recursion for the rescue

We want to convert all ObjectId values to string, the most elegant way of doing this is using recursion:

export const mongooseObjectIdToString = (data: any) => {
  // no value, return value
  if (!data) {
    return data;
  }

  // traverse the array
  if (Array.isArray(data)) {
    return data.map(d => mongooseObjectIdToString(d));
  }

  // transform ObjectId to string
  if (
    ObjectId.isValid(data) &&
    data.toString().indexOf(data) !== -1
  ) {
    return data.toString();
  }

  // traverse nested object 
  if (typeof data === 'object' && !Array.isArray(data)) {
    return Object.keys(data).reduce((prev, curr) => ({
        ...prev,
        [curr]: mongooseObjectIdToString(data[curr]),
      }), {});
  }

  return data;
};
Enter fullscreen mode Exit fullscreen mode

mongooseObjectIdToString will call itself when it finds an array or a nested object, otherwise will just convert the ObjectId to String, or just return any other data type.

The assert would then be:

expect(mongooseObjectIdToString(obj)).toEqual(mongooseObjectIdToString(anotherObj));
Enter fullscreen mode Exit fullscreen mode

In Short

Recursion is not only useful to solve Fibonacci, but can also be applied to solve real problems, like traversing a complex object applying some transforming.

This approach can be generalized to apply any transforming in any complex object, comment with your generalized algorithm, and where you are going to use it.


Woovi
Woovi is a Startup that enables shoppers to pay as they like. To make this possible, Woovi provides instant payment solutions for merchants to accept orders.

If you want to work with us, we are hiring!

Top comments (0)