Node.js Unit Testing with Jest

Streamlined Testing of SQL and XML with Jest in Node.js

Node.js Unit Testing with Jest

Unit testing is essential to ensure the reliability of applications, especially for backend services interacting with databases and structured data like XML. Jest is a powerful JavaScript testing framework that simplifies writing and running tests for Node.js applications.

This guide will walk you through setting up Jest for Node.js, testing SQL interactions, and validating XML structure. By the end, you’ll understand how to mock SQL queries, validate XML, and run tests in Jest with ease.

Prerequisites

To follow along, you should have:

  1. A Node.js application setup.

  2. Basic understanding of SQL and XML.

  3. Jest installed as a development dependency.

Install Jest and Dependencies

Let’s get started by installing Jest, an SQL client (e.g., mssql for SQL Server), and an XML parser (fast-xml-parser).

# Initialize the project and install Jest
npm init -y
npm install --save-dev jest

# Install an SQL client (e.g., `mssql` for SQL Server)
npm install mssql

# Install an XML parser to validate XML structure
npm install fast-xml-parser

Setting Up Jest

Once installed, configure Jest by adding a "test" script in your package.json:

jsonCopy code"scripts": {
  "test": "jest"
}

This setup will enable you to run Jest using npm test from the command line.

Mocking SQL Functions

Let’s create a simple example to test SQL queries without needing an actual database connection. We’ll use mocking to simulate SQL database interactions, allowing us to isolate and test only the code logic.

Step 1: Create a Database Connection Module

First, create a db.js file with a function to connect to your SQL database. This keeps your SQL configuration separate, making it easier to test.

// db.js
const sql = require('mssql');

const config = {
  user: 'yourUsername',
  password: 'yourPassword',
  server: 'yourServer',
  database: 'yourDatabase',
  options: {
    encrypt: true,
    enableArithAbort: true,
  },
};

const connectToDB = async () => {
  try {
    const pool = await sql.connect(config);
    return pool;
  } catch (err) {
    console.error('Database connection failed:', err);
    throw err;
  }
};

module.exports = { connectToDB };

Step 2: Write a Function to Fetch Data

Let’s say we want a function to retrieve a user by ID. This function will run a SQL query to get user information.

// userService.js
const { connectToDB } = require('./db');

const getUserById = async (userId) => {
  const pool = await connectToDB();
  const result = await pool
    .request()
    .input('userId', sql.Int, userId)
    .query('SELECT * FROM Users WHERE id = @userId');
  return result.recordset[0];
};

module.exports = { getUserById };

Step 3: Mock the SQL Database in Jest

To test this function, we’ll use Jest’s jest.mock() method to replace the real database connection with a mock. This lets us test our function without needing an actual database.

// userService.test.js
const sql = require('mssql');
const { getUserById } = require('./userService');

jest.mock('mssql'); // Mock the mssql module

describe('User Service', () => {
  it('should return user data for a valid userId', async () => {
    // Mock response data
    const mockData = { id: 1, name: 'John Doe' };
    const request = { input: jest.fn().mockReturnThis(), query: jest.fn().mockResolvedValue({ recordset: [mockData] }) };
    const pool = { request: jest.fn().mockReturnValue(request) };

    sql.connect.mockResolvedValue(pool); // Mock the connection to return our mock pool

    const result = await getUserById(1);

    // Validate the result
    expect(result).toEqual(mockData);
    expect(pool.request).toHaveBeenCalled();
    expect(request.input).toHaveBeenCalledWith('userId', sql.Int, 1);
    expect(request.query).toHaveBeenCalledWith('SELECT * FROM Users WHERE id = @userId');
  });
});

In this test:

  • We mock the SQL connect() function to return a fake pool object.

  • We mock the query function to return a predefined user (mockData), simulating a successful database query.

  • Jest verifies that the function behaves as expected without hitting a real database.

Testing XML Validation

Next, let’s test XML files to ensure they include required fields. For example, each <user> in the XML might need an <id>, <name>, and <email> field.

Step 1: Write an XML Validator Function

In xmlValidator.js:

const { XMLParser } = require('fast-xml-parser');

const validateMandatoryFields = (xmlData, requiredFields) => {
  const parser = new XMLParser();
  const jsonObj = parser.parse(xmlData);

  // Check each required field
  const missingFields = requiredFields.filter((field) => {
    const parts = field.split('.');
    let obj = jsonObj;
    for (const part of parts) {
      if (!obj || !obj[part]) {
        return true;
      }
      obj = obj[part];
    }
    return false;
  });

  return missingFields.length === 0;
};

module.exports = { validateMandatoryFields };

This validateMandatoryFields function:

  1. Converts the XML into JSON.

  2. Checks if each required field (e.g., user.id, user.name, user.email) is present.

  3. Returns true if all fields exist, otherwise false.

Step 2: Write Jest Tests for XML Validation

Now, let’s create tests to check that validateMandatoryFields works as expected.

In xmlValidator.test.js:

const { validateMandatoryFields } = require('./xmlValidator');

describe('XML Validator', () => {
  it('should return true if all mandatory fields are present', () => {
    const xmlData = `
      <user>
        <id>1</id>
        <name>John Doe</name>
        <email>johndoe@example.com</email>
      </user>
    `;
    const requiredFields = ['user.id', 'user.name', 'user.email'];
    const isValid = validateMandatoryFields(xmlData, requiredFields);

    expect(isValid).toBe(true);
  });

  it('should return false if mandatory fields are missing', () => {
    const xmlData = `
      <user>
        <id>1</id>
        <name>John Doe</name>
      </user>
    `;
    const requiredFields = ['user.id', 'user.name', 'user.email'];
    const isValid = validateMandatoryFields(xmlData, requiredFields);

    expect(isValid).toBe(false);
  });
});

This test suite checks two scenarios:

  • Success case: All required fields are present, so isValid should return true.

  • Failure case: Missing fields cause isValid to return false.

Now, all you have to do is chant the magic incantation:

npm test

Running Jest Tests for Specific Files

To run tests for a particular file, specify the path in the jest command:

npm jest path/to/file.test.js

For example, to run only userService.test.js:

npm jest userService.test.js

You can also use patterns to match filenames:

npm jest user

This command will run all tests with “user” in the filename, making it easy to focus on specific areas during development.

Conclusion

Using Jest to test SQL functions and XML validation in Node.js can improve reliability and make development faster. With Jest’s mocking capabilities, you can avoid real database interactions in tests, and XML validation ensures structured data is complete.

Key Takeaways

  • Mock SQL Connections: Avoid hitting the database during tests by mocking SQL connections.

  • XML Validation: Ensure XML files have all mandatory fields.

  • Run Specific Tests: Run tests for individual files or patterns to quickly validate specific functionality.

With Jest, you’re equipped to write robust, reliable tests that make your Node.js application easier to maintain and extend.