Works with the AI Agents you already use

    Claude CodeClaude CodeCursorCursorCodex CLICodex CLIMuseMuseDotsDots+22 more

    Writing Tests — Spec-Driven, Adversarial, Coverage-Gated

    1

    Writes spec-driven tests (AAA, should_X_when_Y) and runs them through an 8-step pre-commit check with a coverage gate.

    Free

    Security scanned
    Writing Tests — Spec-Driven, Adversarial, Coverage-Gated

    Writing Tests — Spec-Driven, Adversarial, Coverage-Gated

    Example session with this skill installed

    Write unit tests for applyCoupon. The project uses Node's built-in test runner (node:test), ES modules, and tests live in test/unit/ mirroring src/.

    Spec

    • No coupon → subtotal unchanged.
    • Subtotal must be a non-negative integer number of cents, otherwise RangeError.
    • Percent coupons take value% off; the discount is rounded to the nearest cent (half up).
    • Fixed coupons subtract value cents.
    • The total never goes below zero.
    • A coupon is valid through its expiresAt instant (inclusive); after that → COUPON_EXPIRED.
    • Subtotal below minSubtotalCents → MIN_SUBTOTAL_NOT_MET.
    • Unknown coupon type → UNKNOWN_COUPON_TYPE.
    // src/pricing/apply-coupon.js
    export function applyCoupon(subtotalCents, coupon, now = new Date()) {
      if (!Number.isInteger(subtotalCents) || subtotalCents < 0) {
        throw new RangeError('subtotalCents must be a non-negative integer');
      }
      if (!coupon) return subtotalCents;
      if (coupon.expiresAt && now > coupon.expiresAt) {
        throw new Error('COUPON_EXPIRED');
      }
      if (subtotalCents < (coupon.minSubtotalCents ?? 0)) {
        throw new Error('MIN_SUBTOTAL_NOT_MET');
      }
      if (coupon.type === 'percent') {
        return subtotalCents - Math.round((subtotalCents * coupon.value) / 100);
      }
      if (coupon.type === 'fixed') {
        return Math.max(0, subtotalCents - coupon.value);
      }
      throw new Error('UNKNOWN_COUPON_TYPE');
    }
    
    • Read your context and instructions
    • Compiled the writing tests

    Summary. 16 tests written from the spec (6 success, 7 error, 3 edge). 15 pass. One fails, and the code is at fault, not the test: the spec says the total never goes below zero, but a percent coupon with value above 100 returns a negative total (-500 for 150% off 1000 cents). I left the assertion as the spec states it. The task isn't done until the code is fixed.

    These results come from an actual run of the files below on Node.js v24 (Linux), in a scratch copy of the sample.

    Test code: test/unit/pricing/apply-coupon.test.js

    import { describe, it } from 'node:test';
    import assert from 'node:assert/strict';
    import { applyCoupon } from '../../../src/pricing/apply-coupon.js';
    
    const NOW = new Date('2026-10-08T12:00:00Z');
    
    const percentCoupon = (overrides = {}) => ({ type: 'percent', value: 10, ...overrides });
    const fixedCoupon = (overrides = {}) => ({ type: 'fixed', value: 500, ...overrides });
    
    describe('applyCoupon', () => {
      describe('success', () => {
        it('should_return_subtotal_unchanged_when_no_coupon_is_given', () => {
          const result = applyCoupon(2599, null, NOW);
    
          assert.equal(result, 2599);
        });
    
        it('should_take_percentage_off_when_coupon_is_percent', () => {
          const result = applyCoupon(10000, percentCoupon({ value: 15 }), NOW);
    
          assert.equal(result, 8500);
        });
    
        it('should_round_discount_to_nearest_cent_when_percent_yields_fraction', () => {
          // 10% of 1005 = 100.5 cents -> discount rounds to 101
          const result = applyCoupon(1005, percentCoupon({ value: 10 }), NOW);
    
          assert.equal(result, 904);
        });
    
        it('should_subtract_fixed_amount_when_coupon_is_fixed', () => {
          const result = applyCoupon(2000, fixedCoupon({ value: 500 }), NOW);
    
          assert.equal(result, 1500);
        });
    
        it('should_accept_coupon_when_now_equals_expiry_instant', () => {
          const coupon = percentCoupon({ expiresAt: NOW });
    
          const result = applyCoupon(1000, coupon, NOW);
    
          assert.equal(result, 900);
        });
    
        it('should_accept_coupon_when_subtotal_equals_minimum', () => {
          const coupon = fixedCoupon({ value: 500, minSubtotalCents: 3000 });
    
          const result = applyCoupon(3000, coupon, NOW);
    
          assert.equal(result, 2500);
        });
      });
    
      describe('error', () => {
        it('should_throw_coupon_expired_when_now_is_after_expiry', () => {
          const coupon = percentCoupon({ expiresAt: new Date('2026-10-08T11:59:59Z') });
    
          assert.throws(() => applyCoupon(1000, coupon, NOW), { message: 'COUPON_EXPIRED' });
        });
    
        it('should_throw_min_subtotal_not_met_when_subtotal_is_one_cent_below_minimum', () => {
          const coupon = fixedCoupon({ minSubtotalCents: 3000 });
    
          assert.throws(() => applyCoupon(2999, coupon, NOW), { message: 'MIN_SUBTOTAL_NOT_MET' });
        });
    
        it('should_throw_unknown_coupon_type_when_type_is_not_supported', () => {
          const coupon = { type: 'bogo', value: 1 };
    
          assert.throws(() => applyCoupon(1000, coupon, NOW), { message: 'UNKNOWN_COUPON_TYPE' });
        });
    
        const invalidSubtotals = [
          ['negative', -1],
          ['fractional', 10.5],
          ['NaN', Number.NaN],
          ['a_numeric_string', '1000'],
        ];
        for (const [label, bad] of invalidSubtotals) {
          it(`should_throw_range_error_when_subtotal_is_${label}`, () => {
            assert.throws(() => applyCoupon(bad, null, NOW), RangeError);
          });
        }
      });
    
      describe('edge', () => {
        it('should_return_zero_when_fixed_discount_exceeds_subtotal', () => {
          const result = applyCoupon(300, fixedCoupon({ value: 500 }), NOW);
    
          assert.equal(result, 0);
        });
    
        it('should_return_zero_when_subtotal_is_zero', () => {
          const result = applyCoupon(0, percentCoupon(), NOW);
    
          assert.equal(result, 0);
        });
    
        it('should_never_return_negative_total_when_percent_value_exceeds_100', () => {
          // Spec: "the total never goes below zero" — applies to every coupon type.
          const result = applyCoupon(1000, percentCoupon({ value: 150 }), NOW);
    
          assert.ok(result >= 0, `expected a non-negative total, got ${result}`);
        });
      });
    });
    

    Run: random order + coverage (node --test --test-randomize --experimental-test-coverage --test-coverage-include='src/**' --test-coverage-branches=75)

    ✖ should_never_return_negative_total_when_percent_value_exceeds_100
      AssertionError [ERR_ASSERTION]: expected a non-negative total, got -500
    ℹ tests 16
    ℹ pass 15
    ℹ fail 1
    ℹ skipped 0
    ℹ file              | line % | branch % | funcs % | uncovered lines
    ℹ   apply-coupon.js | 100.00 |   100.00 |  100.00 |
    

    Pre-commit steps

    StepResult
    1. Each test in isolation (--test-name-pattern)Same result as the full run: 15 pass, the percent > 100 test fails
    2. Random order (--test-randomize)Ran in random order (two different seeds), same result
    3. All togetherSame result
    4/7. Coverage shown100% line and branch on apply-coupon.js
    5. Linter / formatter / type checkerNot run: none is configured in this sample project
    6. Zero failuresNot met. 1 failure caused by a code defect
    8. OS matrixNot run: Linux only so far

    Coverage summary

    • Success: no coupon, percent, rounding at the half-cent, fixed, expiry instant inclusive, subtotal equal to minimum.
    • Error: expired (one second past), one cent below minimum, unknown type, four invalid subtotals (negative, fractional, NaN, numeric string).
    • Edge: fixed discount larger than subtotal, zero subtotal, percent above 100.
    • Not covered on purpose: the default now = new Date() path. Tests inject the clock to stay deterministic. A negative fixed value is also left out because the spec doesn't define it (see next steps).

    Decisions

    • Every expected value comes from the spec, not from reading the code. The rounding case uses 100.5 cents to check half-up rounding.
    • The clock is injected (NOW), so no test depends on real time.
    • No mocks: the function is pure.
    • Inputs are built with small factories (percentCoupon, fixedCoupon), so each test's setup stays to one or two lines.

    Next steps

    1. Fix the code, not the test: clamp the percent result with Math.max(0, …), or reject value outside 1–100. Which one is a product decision.
    2. Clarify in the spec whether negative coupon values must be rejected, then add the test.
    3. Re-run steps 1–7 after the fix and run the suite in the CI OS matrix.

    Connects securely to your tools. The creator never sees your data.

    About this skill

    Agent-written tests tend to share one flaw: they read the code and assert whatever it already does. The result passes on the first run and finds nothing. This skill writes tests from the spec, aims to break the code, and treats test code with the same quality bar as production code.

    What it does:

    • Pre-test analysis: finds the framework, mocking library, existing fixtures and helpers, linter and type checker already in the project, and reuses them instead of introducing new ones.
    • Coverage planned from the spec: every behavior is listed and sorted into success, error, edge and state-transition cases, then mapped to unit, integration or E2E tests using the pyramid (about 80/20/10 as a guideline).
    • Test style: Arrange-Act-Assert with one Act per test, should_[expected]_when_[condition] names, mocks only at boundaries, no real time, randomness or network in unit tests.
    • Failing tests: when a test fails, it first asks whether the code is wrong. It never silently edits an assertion to match buggy behavior.
    • Coverage gate: 75% branch coverage minimum, 80% target, counted per OS in a cross-platform CI matrix.
    • 8-step pre-commit validation: each test in isolation, random order, all together, coverage, linter/formatter/type checker on test files, zero failures or skips, coverage output shown to you, final checklist.

    Reference files:

    • pyramid-and-mocking.md: layer rules, P0–P3 priority matrix, test doubles, and over-mocking.
    • anti-patterns.md: eight named smells (Mirror, Liar, Giant, Mockery, Inspector, Chain Gang, Flaky, Snowball) with wrong and right examples.
    • file-structure.md: mirrored layout, per-language naming, factories, no catch-all helpers.
    • pre-commit.md: the 8 steps with commands for pytest, Jest, Vitest, Go, Maven and .NET.
    • multi-platform.md: CI matrix with fail-fast: false, and fixes for tests that only work on Linux.

    It triggers on "write tests", "add coverage", "build a test suite", "fix flaky tests" or "verify this with tests".

    Free and MIT-licensed, part of the claude-skills-czl collection.

    Source on GitHub: this skill · full repository

    How to use

    Skills on Agensi run inside your AI agent. Copy the prompt, paste it into your agent, and it connects to Agensi and installs this skill for you. If a payment is needed, your agent shows it to you first.

    Reviews

    No reviews yet

    Be one of the first to try it. Every listed skill passes our trust checks below.

    Security scanned

    Passed our 8-point scan before listing

    Fresh listing

    Recently published to Agensi

    Free forever

    No account required to browse

    Trust & safety

    Security scanned

    Verified clean 11 days ago

    • Free to download with an account

    Requires

    jest
    Node.js
    vitest
    eslint
    prettier
    biome

    Listed11 days ago
    Updated4 days ago

    What's inside

    Frequently Asked Questions