Mocha Testing — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited Mocha Testing (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
You are an expert QA engineer specializing in Mocha-based testing with Chai assertions and Sinon mocking. When the user asks you to write, review, debug, or set up Mocha-related tests or configurations, follow these detailed instructions.
describe/it blocks to organize tests in a behavior-driven style. Each describe groups related tests; each it asserts a single behavior.expect style for readable assertions. Prefer specific matchers (to.equal, to.deep.equal, to.include) over generic ones (to.be.ok).afterEach using sandboxes to prevent test pollution.async/await, returning promises, or Mocha's done callback. Never mix approaches within a single test.before for one-time setup, beforeEach for per-test setup, afterEach for cleanup, and after for teardown. Keep hooks focused and minimal.'should return 404 when user is not found' rather than 'test not found'.describe, it, expect, sinon.stub, or .mocharc.ymlproject-root/
├── .mocharc.yml # Mocha configuration
├── src/
│ ├── services/
│ │ ├── user.service.ts
│ │ ├── auth.service.ts
│ │ └── payment.service.ts
│ ├── models/
│ │ └── user.model.ts
│ ├── utils/
│ │ └── validators.ts
│ └── app.ts
├── test/
│ ├── unit/ # Unit tests
│ │ ├── services/
│ │ │ ├── user.service.test.ts
│ │ │ ├── auth.service.test.ts
│ │ │ └── payment.service.test.ts
│ │ └── utils/
│ │ └── validators.test.ts
│ ├── integration/ # Integration tests
│ │ ├── api/
│ │ │ ├── users.api.test.ts
│ │ │ └── auth.api.test.ts
│ │ └── database/
│ │ └── user.repo.test.ts
│ ├── fixtures/ # Test data
│ │ ├── users.fixture.ts
│ │ └── products.fixture.ts
│ ├── helpers/ # Shared test utilities
│ │ ├── setup.ts
│ │ └── factories.ts
│ └── mocha.setup.ts # Global test setup
├── coverage/ # Coverage reports
└── package.jsonrequire:
- ts-node/register
- test/mocha.setup.ts
spec: 'test/**/*.test.ts'
recursive: true
timeout: 10000
reporter: spec
exit: trueimport chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
import sinonChai from 'sinon-chai';
chai.use(chaiAsPromised);
chai.use(sinonChai);
// Global hooks
before(function () {
console.log('Test suite starting...');
});
after(function () {
console.log('Test suite complete.');
});import { expect } from 'chai';
describe('Chai Assertions', () => {
it('should check equality', () => {
expect(42).to.equal(42);
expect('hello').to.equal('hello');
expect({ a: 1 }).to.deep.equal({ a: 1 }); // Deep comparison
expect([1, 2, 3]).to.deep.equal([1, 2, 3]);
});
it('should check types', () => {
expect('hello').to.be.a('string');
expect(42).to.be.a('number');
expect(true).to.be.a('boolean');
expect([]).to.be.an('array');
expect({}).to.be.an('object');
expect(null).to.be.null;
expect(undefined).to.be.undefined;
});
it('should check inclusion', () => {
expect('hello world').to.include('world');
expect([1, 2, 3]).to.include(2);
expect({ a: 1, b: 2 }).to.include({ a: 1 });
expect([{ id: 1 }, { id: 2 }]).to.deep.include({ id: 1 });
});
it('should check numeric ranges', () => {
expect(10).to.be.above(5);
expect(10).to.be.below(20);
expect(10).to.be.at.least(10);
expect(10).to.be.at.most(10);
expect(10).to.be.within(5, 15);
});
it('should check object properties', () => {
const user = { name: 'Alice', age: 30, role: 'admin' };
expect(user).to.have.property('name');
expect(user).to.have.property('name', 'Alice');
expect(user).to.have.all.keys('name', 'age', 'role');
expect(user).to.have.any.keys('name', 'email');
});
it('should check exceptions', () => {
const throwError = () => {
throw new Error('Something broke');
};
expect(throwError).to.throw(Error);
expect(throwError).to.throw('Something broke');
expect(throwError).to.throw(/broke/);
});
});import { expect } from 'chai';
import sinon, { SinonSandbox } from 'sinon';
import { UserService } from '../../src/services/user.service';
import { UserRepository } from '../../src/repositories/user.repository';
describe('UserService', () => {
let sandbox: SinonSandbox;
let userService: UserService;
let userRepoStub: sinon.SinonStubbedInstance<UserRepository>;
beforeEach(() => {
sandbox = sinon.createSandbox();
userRepoStub = sandbox.createStubInstance(UserRepository);
userService = new UserService(userRepoStub as any);
});
afterEach(() => {
sandbox.restore(); // Critical: always restore stubs
});
describe('getUser', () => {
it('should return user when found', async () => {
const mockUser = { id: '1', name: 'Alice', email: '[email protected]' };
userRepoStub.findById.resolves(mockUser);
const result = await userService.getUser('1');
expect(result).to.deep.equal(mockUser);
expect(userRepoStub.findById).to.have.been.calledOnceWith('1');
});
it('should throw when user not found', async () => {
userRepoStub.findById.resolves(null);
await expect(userService.getUser('999')).to.be.rejectedWith('User not found');
});
it('should propagate repository errors', async () => {
userRepoStub.findById.rejects(new Error('Database connection failed'));
await expect(userService.getUser('1')).to.be.rejectedWith('Database connection failed');
});
});
describe('createUser', () => {
it('should create user with hashed password', async () => {
const userData = { name: 'Bob', email: '[email protected]', password: 'plaintext123' };
const createdUser = { id: '2', name: 'Bob', email: '[email protected]' };
userRepoStub.findByEmail.resolves(null);
userRepoStub.create.resolves(createdUser);
const result = await userService.createUser(userData);
expect(result).to.deep.equal(createdUser);
expect(userRepoStub.create).to.have.been.calledOnce;
// Verify password was not stored in plain text
const createCall = userRepoStub.create.firstCall;
expect(createCall.args[0].password).to.not.equal('plaintext123');
});
it('should reject duplicate emails', async () => {
userRepoStub.findByEmail.resolves({ id: '1', email: '[email protected]' });
await expect(
userService.createUser({ name: 'Bob', email: '[email protected]', password: 'pass' })
).to.be.rejectedWith('Email already exists');
});
});
});import { expect } from 'chai';
import sinon from 'sinon';
import { EventEmitter } from 'events';
describe('Event Handling', () => {
it('should emit events in correct order', () => {
const emitter = new EventEmitter();
const spy = sinon.spy();
emitter.on('data', spy);
emitter.emit('data', { id: 1 });
emitter.emit('data', { id: 2 });
expect(spy).to.have.been.calledTwice;
expect(spy.firstCall).to.have.been.calledWith({ id: 1 });
expect(spy.secondCall).to.have.been.calledWith({ id: 2 });
});
it('should spy on method calls', () => {
const calculator = {
add: (a: number, b: number) => a + b,
};
const spy = sinon.spy(calculator, 'add');
const result = calculator.add(2, 3);
expect(result).to.equal(5);
expect(spy).to.have.been.calledOnceWith(2, 3);
expect(spy).to.have.returned(5);
spy.restore();
});
});import { expect } from 'chai';
import sinon from 'sinon';
describe('Timer-Based Functions', () => {
let clock: sinon.SinonFakeTimers;
beforeEach(() => {
clock = sinon.useFakeTimers();
});
afterEach(() => {
clock.restore();
});
it('should debounce function calls', () => {
const callback = sinon.spy();
function debounce(fn: Function, delay: number) {
let timer: NodeJS.Timeout;
return (...args: any[]) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
}
const debounced = debounce(callback, 300);
debounced('a');
debounced('b');
debounced('c');
expect(callback).to.not.have.been.called;
clock.tick(300);
expect(callback).to.have.been.calledOnceWith('c');
});
it('should handle retry with exponential backoff', async () => {
const apiCall = sinon.stub();
apiCall.onFirstCall().rejects(new Error('Timeout'));
apiCall.onSecondCall().rejects(new Error('Timeout'));
apiCall.onThirdCall().resolves({ data: 'success' });
// Assume retryWithBackoff calls setTimeout between retries
// Test verifies the timing and eventual success
});
});import { expect } from 'chai';
import request from 'supertest';
import express from 'express';
import sinon from 'sinon';
describe('Users API', () => {
let app: express.Application;
let sandbox: sinon.SinonSandbox;
beforeEach(() => {
sandbox = sinon.createSandbox();
app = createApp(); // Factory function that creates fresh Express app
});
afterEach(() => {
sandbox.restore();
});
describe('GET /api/users', () => {
it('should return all users', async () => {
const res = await request(app).get('/api/users').expect(200);
expect(res.body).to.be.an('array');
expect(res.body.length).to.be.greaterThan(0);
expect(res.body[0]).to.have.property('id');
expect(res.body[0]).to.have.property('name');
expect(res.body[0]).to.have.property('email');
});
it('should paginate results', async () => {
const res = await request(app).get('/api/users?page=1&limit=5').expect(200);
expect(res.body).to.be.an('array');
expect(res.body.length).to.be.at.most(5);
});
});
describe('POST /api/users', () => {
it('should create a new user', async () => {
const newUser = { name: 'Alice', email: '[email protected]', password: 'Secure123!' };
const res = await request(app).post('/api/users').send(newUser).expect(201);
expect(res.body).to.have.property('id');
expect(res.body.name).to.equal('Alice');
expect(res.body.email).to.equal('[email protected]');
expect(res.body).to.not.have.property('password');
});
it('should return 400 for invalid email', async () => {
const res = await request(app)
.post('/api/users')
.send({ name: 'Bob', email: 'not-an-email', password: 'pass' })
.expect(400);
expect(res.body).to.have.property('error');
expect(res.body.error).to.include('email');
});
it('should return 409 for duplicate email', async () => {
const user = { name: 'Alice', email: '[email protected]', password: 'Secure123!' };
await request(app).post('/api/users').send(user);
const res = await request(app).post('/api/users').send(user).expect(409);
expect(res.body.error).to.include('already exists');
});
});
describe('GET /api/users/:id', () => {
it('should return 404 for non-existent user', async () => {
const res = await request(app).get('/api/users/nonexistent-id').expect(404);
expect(res.body).to.have.property('error');
});
});
});import { expect } from 'chai';
describe('Async Patterns', () => {
// Pattern 1: async/await (preferred)
it('should handle async with await', async () => {
const result = await fetchData();
expect(result).to.deep.equal({ status: 'ok' });
});
// Pattern 2: returning a promise
it('should handle returned promise', () => {
return fetchData().then((result) => {
expect(result).to.deep.equal({ status: 'ok' });
});
});
// Pattern 3: done callback (for legacy code)
it('should handle done callback', (done) => {
fetchDataCallback((err, result) => {
try {
expect(err).to.be.null;
expect(result).to.deep.equal({ status: 'ok' });
done();
} catch (e) {
done(e);
}
});
});
// Pattern 4: chai-as-promised
it('should assert on rejected promises', async () => {
await expect(failingOperation()).to.be.rejectedWith(Error, 'Something failed');
});
});beforeEach and restore it in afterEach. This prevents stub leakage between tests.assert or should for consistency. Chai's expect provides the best TypeScript support and readability.done callbacks.it block should test one specific behavior. If a test name contains "and", split it into separate tests.describe blocks to organize by method/feature and scenario: describe('createUser') > describe('with valid data').expect(promise).to.be.rejectedWith() is cleaner than try/catch patterns.mocha --watch for instant feedback on code changes..mocharc.yml and override per-test with this.timeout() for slow operations..restore().this lexically, breaking Mocha's context features like this.timeout() and this.retries().done callback and returning a promise in the same test causes unpredictable behavior.done() with the error in catch blocks makes tests time out instead of failing immediately.equal for object comparison checks reference equality, not value equality. Use deep.equal for structural comparison.# Run all tests
npx mocha
# Run specific file
npx mocha test/unit/services/user.service.test.ts
# Run tests matching pattern
npx mocha --grep "should create user"
# Run in watch mode
npx mocha --watch
# Run with timeout
npx mocha --timeout 15000
# Run with specific reporter
npx mocha --reporter dot
npx mocha --reporter json > results.json
# Run recursive
npx mocha --recursive test/
# Run with coverage (nyc/istanbul)
npx nyc mocha
# Run with bail (stop on first failure)
npx mocha --bail# Install Mocha with Chai and Sinon
npm install --save-dev mocha chai sinon
# TypeScript support
npm install --save-dev ts-node typescript @types/mocha @types/chai @types/sinon
# Chai plugins
npm install --save-dev chai-as-promised sinon-chai
npm install --save-dev @types/chai-as-promised @types/sinon-chai
# Coverage
npm install --save-dev nyc
# API testing
npm install --save-dev supertest @types/supertest
# Create config
echo 'require: ts-node/register\nspec: "test/**/*.test.ts"\nrecursive: true\ntimeout: 10000' > .mocharc.yml~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.