cyberangles blog

How to Test if an Object Contains At Least Another Object's Structure with Chai Assertions in Mocha Unit Tests

In modern JavaScript development, objects are ubiquitous—whether representing API responses, state management data, or complex configurations. Ensuring these objects have the expected structure (i.e., specific properties with matching values, even if they contain additional data) is critical for robust applications.

Unit testing frameworks like Mocha (a test runner) and Chai (an assertion library) simplify this process. Chai’s powerful assertion syntax allows you to verify that one object "contains" another object’s structure, meaning the target object has all the properties (and nested properties) of the expected object, but may include extra properties.

This blog will guide you through testing object structures with Chai and Mocha, covering basic use cases, nested objects, arrays, edge cases, and best practices.

2025-12

Table of Contents#

  1. Prerequisites
  2. Setup: Project Configuration
  3. Core Concept: "Contains" vs. "Equals"
  4. Basic Example: Simple Objects
  5. Testing Nested Objects
  6. Handling Arrays of Objects
  7. Edge Cases to Consider
  8. Best Practices
  9. Conclusion
  10. References

Prerequisites#

Before getting started, ensure you have:

  • Basic knowledge of JavaScript (ES6+).
  • Node.js (v14+ recommended) and npm/yarn installed.
  • Familiarity with unit testing concepts (test runners, assertions).

Setup: Project Configuration#

Let’s set up a minimal project to demonstrate object structure testing.

Step 1: Initialize a Project#

Create a new directory and initialize npm:

mkdir object-structure-testing && cd object-structure-testing  
npm init -y  

Step 2: Install Dependencies#

Install Mocha (test runner) and Chai (assertion library) as dev dependencies:

npm install --save-dev mocha chai  

Step 3: Configure Mocha#

Update package.json to include a test script:

{  
  "scripts": {  
    "test": "mocha test/**/*.test.js"  
  }  
}  

This tells Mocha to run all .test.js files in the test directory.

Step 4: Create a Test File#

Create a test directory and a test file:

mkdir test && touch test/object-structure.test.js  

Core Concept: "Contains" vs. "Equals"#

Before diving into examples, it’s critical to distinguish between two common Chai assertions:

AssertionBehavior
expect(actual).to.deep.equal(expected)Checks if actual and expected are exactly identical (same properties, values, and no extra properties).
expect(actual).to.deep.include(expected)Checks if actual contains all properties and values of expected (but may have additional properties).

For structural testing, we want deep.include: it ensures the target object has the required properties without enforcing strict equality.

Basic Example: Simple Objects#

Let’s start with a basic scenario: testing that an object contains a subset of properties.

Test Case#

Suppose we have a function getUser() that returns a user object. We want to verify it always includes a name property (but may have extra properties like age or email).

Implementation#

In test/object-structure.test.js, add:

const { expect } = require('chai');  
 
// Mock function returning a user object  
const getUser = () => ({  
  name: 'Alice',  
  age: 30,  
  email: '[email protected]'  
});  
 
describe('getUser()', () => {  
  it('should return an object containing a "name" property', () => {  
    const actualUser = getUser();  
    const expectedStructure = { name: 'Alice' };  
 
    // Assert actualUser contains expectedStructure  
    expect(actualUser).to.deep.include(expectedStructure);  
  });  
});  

Explanation#

  • actualUser is { name: 'Alice', age: 30, email: '[email protected]' }.
  • expectedStructure is { name: 'Alice' }.
  • expect(actualUser).to.deep.include(expectedStructure) passes because actualUser has the name property with value 'Alice' (it ignores age and email).

What If It Fails?#

If getUser() accidentally returns { name: 'Bob', age: 30 }, the test will fail with:

AssertionError: expected { name: 'Bob', age: 30, email: '[email protected]' } to deeply include { name: 'Alice' }  

Testing Nested Objects#

Most real-world objects are nested (e.g., user.address.city). Chai’s deep.include handles nested structures seamlessly.

Test Case#

Suppose getUser() now returns a nested object:

const getUser = () => ({  
  name: 'Bob',  
  address: {  
    city: 'Paris',  
    zip: '75001',  
    country: 'France'  
  },  
  hobbies: ['reading', 'hiking']  
});  

We want to verify the nested address object contains city: 'Paris'.

Implementation#

Add this test to the describe block:

it('should return an object with nested "address.city" property', () => {  
  const actualUser = getUser();  
  const expectedNestedStructure = {  
    address: {  
      city: 'Paris'  
    }  
  };  
 
  expect(actualUser).to.deep.include(expectedNestedStructure);  
});  

Explanation#

  • expectedNestedStructure specifies that actualUser must have an address object with a city property set to 'Paris'.
  • deep.include recursively checks nested properties: even if address has extra properties like zip or country, the test passes as long as city: 'Paris' exists.

Handling Arrays of Objects#

Arrays of objects (e.g., lists of users or products) require special handling. Chai provides tools to check if an array contains an object with the expected structure.

Scenario 1: Array Contains an Object with Expected Structure#

Suppose we have an array of products:

const getProducts = () => [  
  { id: 1, name: 'Laptop', price: 999 },  
  { id: 2, name: 'Phone', price: 699 },  
  { id: 3, name: 'Tablet', price: 299 }  
];  

We want to verify the array includes a product with id: 2 (regardless of other properties).

Implementation#

Use include.something.that.deep.includes to check if any element in the array contains the expected structure:

describe('getProducts()', () => {  
  it('should include a product with id: 2', () => {  
    const products = getProducts();  
    const expectedProduct = { id: 2 };  
 
    // Check if any product in the array includes { id: 2 }  
    expect(products).to.include.something.that.deep.includes(expectedProduct);  
  });  
});  

Scenario 2: Array Contains All Expected Objects#

To verify an array contains all expected objects (in any order), use deep.include.members:

it('should include products with ids 1 and 3', () => {  
  const products = getProducts();  
  const expectedProducts = [  
    { id: 1 }, // Matches first product  
    { id: 3 }  // Matches third product  
  ];  
 
  expect(products).to.deep.include.members(expectedProducts);  
});  

Edge Cases to Consider#

1. Missing Properties#

If actual lacks a required property from expected, the test fails. For example:

const actual = { name: 'Alice' };  
const expected = { name: 'Alice', age: 30 };  
expect(actual).to.deep.include(expected); // Fails: "age" is missing  

2. Type Mismatch#

deep.include enforces strict type matching. A string age: '30' will fail if expected has age: 30 (number):

const actual = { name: 'Alice', age: '30' }; // String "30"  
const expected = { age: 30 }; // Number 30  
expect(actual).to.deep.include(expected); // Fails: type mismatch  

3. Null/Undefined Values#

If expected includes null or undefined, actual must match exactly:

const actual = { name: null };  
const expected = { name: null };  
expect(actual).to.deep.include(expected); // Passes  
 
const actual2 = { name: undefined };  
const expected2 = { name: null };  
expect(actual2).to.deep.include(expected2); // Fails: undefined ≠ null  

Best Practices#

  1. Be Specific with expected: Only include critical properties in expected to avoid over-testing. For example, if email is optional, don’t include it in expected.

  2. Test Nested Structures Explicitly: Always test nested properties (e.g., user.address.city) to catch regressions in deep object hierarchies.

  3. Avoid Overusing deep.equal: Reserve deep.equal for cases where strict equality is required (e.g., configuration objects with fixed keys).

  4. Use deep.include for Partial Matches: Prefer deep.include when testing API responses or dynamic data, where extra fields are common.

Conclusion#

Testing object structures ensures your application’s data adheres to required formats, preventing runtime errors and ensuring consistency. With Chai’s deep.include assertion and Mocha’s test runner, you can easily verify that objects contain the expected properties—even nested ones or those within arrays.

By following the patterns in this guide, you’ll write more maintainable tests that focus on critical structure rather than strict equality, making your test suite resilient to harmless changes in data.

References#