You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何结合流、Promise与管道编写单元测试?Mocha+Sinon测试方法

Hey there! Let's break down your two questions one by one—both are super common when writing robust Node.js tests, so I'll walk you through practical examples to make it clear.

1. Testing Functions That Combine Streams, Promises, and Pipes

Streams are event-driven, which can make testing tricky, but wrapping them in Promises (or using Node.js's built-in promise-based stream utilities) lets you use familiar async/await syntax for cleaner tests.

Key Approaches & Examples

Convert Streams to Promises for Easier Async Testing

Node.js v15+ includes stream/promises, which turns stream operations like pipeline into Promise-based functions. This eliminates manual event handling for finish/error events.

Suppose you have a function that reads a file, transforms its content to uppercase, and writes it to an output file via pipes:

const { createReadStream, createWriteStream } = require('fs');
const { pipeline } = require('stream/promises');
const { Transform } = require('stream');

async function processFileStream(inputPath, outputPath) {
  const upperCaseTransform = new Transform({
    transform(chunk, enc, cb) {
      cb(null, chunk.toString().toUpperCase());
    }
  });

  return pipeline(
    createReadStream(inputPath),
    upperCaseTransform,
    createWriteStream(outputPath)
  );
}

Here's how to test it with Mocha and Chai, using temporary files to avoid polluting your filesystem:

const { expect } = require('chai');
const fs = require('fs/promises');
const path = require('path');
const processFileStream = require('../src/processFileStream');

describe('processFileStream', () => {
  let tempInput, tempOutput;

  // Create test files before each test
  beforeEach(async () => {
    tempInput = path.join(__dirname, 'temp-input.txt');
    await fs.writeFile(tempInput, 'hello world');
    tempOutput = path.join(__dirname, 'temp-output.txt');
  });

  // Clean up after each test
  afterEach(async () => {
    await fs.unlink(tempInput).catch(() => {});
    await fs.unlink(tempOutput).catch(() => {});
  });

  it('converts input text to uppercase and writes to output', async () => {
    await processFileStream(tempInput, tempOutput);
    const outputContent = await fs.readFile(tempOutput, 'utf8');
    expect(outputContent).to.equal('HELLO WORLD');
  });

  it('rejects with an error if the input file does not exist', async () => {
    await expect(processFileStream('non-existent-file.txt', tempOutput))
      .to.be.rejectedWith('ENOENT: no such file or directory');
  });
});

Test with In-Memory Streams for Speed

For faster tests (no disk I/O), use PassThrough streams to simulate input/output without files:

const { PassThrough } = require('stream');

// Modified function to accept streams instead of file paths
async function processStream(inputStream, outputStream) {
  const upperCaseTransform = new Transform({
    transform(chunk, enc, cb) {
      cb(null, chunk.toString().toUpperCase());
    }
  });

  return pipeline(inputStream, upperCaseTransform, outputStream);
}

// Corresponding test
it('transforms stream data to uppercase', async () => {
  const input = new PassThrough();
  const output = new PassThrough();
  const chunks = [];

  output.on('data', chunk => chunks.push(chunk));

  // Start the pipeline
  processStream(input, output);

  // Send test data
  input.write('hello');
  input.end();

  // Wait for the stream to finish
  await new Promise(resolve => output.on('finish', resolve));

  expect(Buffer.concat(chunks).toString()).to.equal('HELLO');
});
2. Writing Unit Tests for Your Own Functions with Mocha and Sinon

Mocha handles test organization and execution, while Sinon helps you mock/stub external dependencies so you can isolate your function's logic.

Setup First

Install the required packages:

npm install mocha sinon chai --save-dev

Add a test script to your package.json:

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

Test Scenarios & Examples

Testing Synchronous Functions

For simple, pure functions, tests are straightforward:

// src/math.js
function multiply(a, b) {
  return a * b;
}

// test/math.test.js
const { expect } = require('chai');
const multiply = require('../src/math');

describe('multiply function', () => {
  it('returns the product of two numbers', () => {
    expect(multiply(3, 4)).to.equal(12);
    expect(multiply(-2, 5)).to.equal(-10);
  });
});

Testing Asynchronous Functions (Callbacks)

If your function uses callbacks, use Sinon to stub external dependencies and verify interactions:

// src/fileUtils.js
const fs = require('fs');

function getFileSize(path, callback) {
  fs.stat(path, (err, stats) => {
    if (err) return callback(err);
    callback(null, stats.size);
  });
}

// test/fileUtils.test.js
const { expect } = require('chai');
const sinon = require('sinon');
const fs = require('fs');
const getFileSize = require('../src/fileUtils');

describe('getFileSize', () => {
  let statStub;

  // Replace fs.stat with a stub before each test
  beforeEach(() => {
    statStub = sinon.stub(fs, 'stat');
  });

  // Restore the original function after each test
  afterEach(() => {
    statStub.restore();
  });

  it('returns the file size when the file exists', (done) => {
    statStub.callsArgWith(1, null, { size: 1024 });

    getFileSize('test.txt', (err, size) => {
      expect(err).to.be.null;
      expect(size).to.equal(1024);
      // Verify the stub was called with the right arguments
      sinon.assert.calledOnceWithExactly(statStub, 'test.txt', sinon.match.func);
      done(); // Signal Mocha the async test is complete
    });
  });

  it('passes an error to the callback if the file is missing', (done) => {
    const testError = new Error('File not found');
    statStub.callsArgWith(1, testError);

    getFileSize('missing.txt', (err) => {
      expect(err).to.equal(testError);
      done();
    });
  });
});

Testing Asynchronous Functions (Promises)

For Promise-based functions, use async/await with Sinon's stub resolvers/rejecters:

// src/fileUtils.js (Promise version)
const fs = require('fs/promises');

async function getFileSize(path) {
  const stats = await fs.stat(path);
  return stats.size;
}

// test/fileUtils.test.js
const { expect } = require('chai');
const sinon = require('sinon');
const fs = require('fs/promises');
const getFileSize = require('../src/fileUtils');

describe('getFileSize', () => {
  let statStub;

  beforeEach(() => {
    statStub = sinon.stub(fs, 'stat');
  });

  afterEach(() => {
    statStub.restore();
  });

  it('returns the file size when the file exists', async () => {
    statStub.resolves({ size: 2048 });

    const size = await getFileSize('test.txt');
    expect(size).to.equal(2048);
    sinon.assert.calledOnceWithExactly(statStub, 'test.txt');
  });

  it('rejects with an error if the file is missing', async () => {
    const testError = new Error('File not found');
    statStub.rejects(testError);

    await expect(getFileSize('missing.txt')).to.be.rejectedWith(testError);
  });
});

Using Sinon Spies to Track Function Calls

Spies let you verify if a function was called, how many times, and with what arguments:

// src/notification.js
function sendEmail(email, message) {
  // Real email sending logic
}

function notifyUser(user, message) {
  sendEmail(user.email, message);
}

// test/notification.test.js
const { expect } = require('chai');
const sinon = require('sinon');
const { notifyUser, sendEmail } = require('../src/notification');

describe('notifyUser', () => {
  let sendEmailSpy;

  beforeEach(() => {
    sendEmailSpy = sinon.spy(sendEmail);
  });

  it('calls sendEmail with the user\'s email and message', () => {
    const testUser = { email: 'user@example.com' };
    const testMessage = 'Hello there!';

    notifyUser(testUser, testMessage);
    sinon.assert.calledOnceWithExactly(sendEmailSpy, 'user@example.com', 'Hello there!');
  });
});

内容的提问来源于stack exchange,提问作者Shamoon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 10:13:45