JavaScript Unit Testing JSON Schema Validation

Used extensively in JavaScript projects, both frontend and backend, JSON schema validation is a well-known technique to verify that the expected data is in the right shape.

Should we even write unit tests for the logic using JSON schema validation? If so, how?

Case for Writing Unit Tests for JSON Schema Validation

As with any code, the one using JSON schema validation also requires guardrails in the form of unit tests to ensure that the code works correctly and detects any unwanted breaking changes in the future. Here’s an example of a schema that makes it mandatory to have firstName, middleName, and lastName of the user and forbids adding additional fields.

const userSchema = {
  type: "object",
  properties: {
    firstName: { type: "string", minLength: 3, maxLength: 32 },
    middleName: { type: "string", minLength: 1, maxLength: 32 },
    lastName: { type: "string", minLength: 3, maxLength: 32 },
    age: { type: "integer", minimum: 1, maximum: 150 },
  },
  required: ["firstName", "middleName", "lastName"],
  additionalProperties: false,
};

This is all well and according to our expectations. We know the JSON schema validator will ensure these fields are present and what should be their minimum and maximum values. But what happens when someone unintentionally and mistakenly changes this schema and, say, removes the firstName required and modifies its minimum length requirement?

The answer is that it will break the existing logic or corrupt the stored data. Either way, a test suite will ensure this mistake doesn’t happen.

How to Unit Test Schema Validation?

We can assume that any JSON schema validator library will follow the official specs. Therefore, all we need to know is our data validation requirements and knowledge of JSON schema syntax, and then ensure that the schema is in the right shape (having no interest in the implementation details of the library and surety that the validator will do its validation according to the specs). This is also better because we’re not coupled with any specific library and how it returns validation responses.

We need to test that the firstName, middleName, and lastName require strings of particular lengths and that no additional fields should be allowed. We also need to test the age schema.

Using jest, here’s how:

const userSchema = {
  type: "object",
  properties: {
    firstName: { type: "string", minLength: 3, maxLength: 32 },
    middleName: { type: "string", minLength: 1, maxLength: 32 },
    lastName: { type: "string", minLength: 3, maxLength: 32 },
    age: { type: "integer", minimum: 1, maximum: 150 },
  },
  required: ["firstName", "middleName", "lastName"],
  additionalProperties: false,
};

describe("User JSON schema validation", () => {
  describe("`firstName`", () => {
    test('`firstName` is required', () => {
      expect(userSchema.required.includes("firstName")).toBe(true);
    });

    test('`firstName` is a string', () => {
      expect(userSchema.properties.firstName.type).toBe("string");
    });

    test('`firstName` minimum length 3', () => {
      expect(userSchema.properties.firstName.minLength).toBe(3);
    });

    test('`firstName` maximum length 32', () => {
      expect(userSchema.properties.firstName.maxLength).toBe(32);
    });
  });

  describe("`middleName`", () => {
    test('`middleName` is required', () => {
      expect(userSchema.required.includes("middleName")).toBe(true);
    });

    test('`middleName` is a string', () => {
      expect(userSchema.properties.middleName.type).toBe("string");
    });

    test('`middleName` minimum length 1', () => {
      expect(userSchema.properties.middleName.minLength).toBe(1);
    });

    test('`middleName` maximum length 32', () => {
      expect(userSchema.properties.middleName.maxLength).toBe(32);
    });
  });

  describe("`lastName`", () => {
    test('`lastName` is required', () => {
      expect(userSchema.required.includes("lastName")).toBe(true);
    });

    test('`lastName` is a string', () => {
      expect(userSchema.properties.lastName.type).toBe("string");
    });

    test('`lastName` minimum length 3', () => {
      expect(userSchema.properties.lastName.minLength).toBe(3);
    });

    test('`lastName` maximum length 32', () => {
      expect(userSchema.properties.lastName.maxLength).toBe(32);
    });
  });

  describe("`age`", () => {
    test('`age` is not required', () => {
      expect(userSchema.required.includes("age")).toBe(false);
    });

    test('`age` is a integer', () => {
      expect(userSchema.properties.age.type).toBe("integer");
    });

    test('`age` minimum is 1', () => {
      expect(userSchema.properties.age.minimum).toBe(1);
    });

    test('`age` maximum is 150', () => {
      expect(userSchema.properties.age.maximum).toBe(150);
    });
  });

  describe("additional properties", () => {
    test('no additional properties allowed', () => {
      expect(userSchema.additionalProperties).toBe(false);
    });
  });
});

Running this test file will result in:


  PASS  ./index.test.js
  User JSON schema validation
    `firstName`
      ✓ `firstName` is required (3 ms)
      ✓ `firstName` is a string
      ✓ `firstName` minimum length 3 (1 ms)
      ✓ `firstName` maximum length 32
    `middleName`
      ✓ `middleName` is required (1 ms)
      ✓ `middleName` is a string
      ✓ `middleName` minimum length 1
      ✓ `middleName` maximum length 32
    `lastName`
      ✓ `lastName` is required
      ✓ `lastName` is a string (1 ms)
      ✓ `lastName` minimum length 3
      ✓ `lastName` maximum length 32
    `age`
      ✓ `age` is not required
      ✓ `age` is a integer
      ✓ `age` minimum is 1
      ✓ `age` maximum is 150
    additional properties
      ✓ no additional properties allowed

  Test Suites: 1 passed, 1 total
  Tests:       17 passed, 17 total
  Snapshots:   0 total
  Time:        0.389 s, estimated 1 s

If someone were to remove the minLength requirement for firstName, the test would fail.




See also

When you purchase through links on techighness.com, I may earn an affiliate commission.