{"id":17436155,"url":"https://github.com/oclif/fancy-test","last_synced_at":"2025-06-23T12:02:14.954Z","repository":{"id":38992389,"uuid":"118292164","full_name":"oclif/fancy-test","owner":"oclif","description":"extends mocha with helpful, chainable extensions","archived":true,"fork":false,"pushed_at":"2024-05-22T18:32:31.000Z","size":1475,"stargazers_count":60,"open_issues_count":14,"forks_count":9,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-06-19T01:12:53.279Z","etag":null,"topics":["javascript","mocha","nodejs","typescript"],"latest_commit_sha":null,"homepage":"https://npmjs.com/package/fancy-test","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/oclif.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-01-21T00:28:16.000Z","updated_at":"2025-03-18T21:49:02.000Z","dependencies_parsed_at":"2023-11-15T21:30:53.146Z","dependency_job_id":"9ef6187a-913f-4c69-afdf-29e128583c07","html_url":"https://github.com/oclif/fancy-test","commit_stats":{"total_commits":314,"total_committers":15,"mean_commits":"20.933333333333334","dds":0.4968152866242038,"last_synced_commit":"c6d1325a4bd0346d68f3ac235ccf802a67f6d253"},"previous_names":["jdxcode/fancy-test","jdxcode/fancy-mocha"],"tags_count":130,"template":false,"template_full_name":null,"purl":"pkg:github/oclif/fancy-test","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oclif%2Ffancy-test","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oclif%2Ffancy-test/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oclif%2Ffancy-test/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oclif%2Ffancy-test/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/oclif","download_url":"https://codeload.github.com/oclif/fancy-test/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oclif%2Ffancy-test/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":261475828,"owners_count":23164053,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["javascript","mocha","nodejs","typescript"],"created_at":"2024-10-17T10:04:34.493Z","updated_at":"2025-06-23T12:02:09.932Z","avatar_url":"https://github.com/oclif.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"fancy-test\n===========\n\n⚠️ **This library has been deprecated.** ⚠️\n\nAs of [v4](https://github.com/oclif/test/releases/tag/4.0.1) `@oclif/test` no longer depends on fancy-test so we've deprecated the npm package.\n\nextendable utilities for testing\n\n[![Version](https://img.shields.io/npm/v/fancy-test.svg)](https://npmjs.org/package/fancy-test)\n[![Known Vulnerabilities](https://snyk.io/test/npm/fancy-test/badge.svg)](https://snyk.io/test/npm/fancy-test)\n[![Downloads/week](https://img.shields.io/npm/dw/fancy-test.svg)](https://npmjs.org/package/fancy-test)\n[![License](https://img.shields.io/npm/l/fancy-test.svg)](https://github.com/jdxcode/fancy-test/blob/main/package.json)\n\n\u003c!-- toc --\u003e\n\n- [fancy-test](#fancy-test)\n- [Why](#why)\n- [V3 Breaking Changes](#v3-breaking-changes)\n- [Usage](#usage)\n  - [Stub](#stub)\n  - [Catch](#catch)\n  - [Finally](#finally)\n  - [Nock](#nock)\n  - [Environment Variables](#environment-variables)\n  - [Do](#do)\n  - [Add](#add)\n  - [Stdin Mocking](#stdin-mocking)\n  - [Stdout/Stderr Mocking](#stdoutstderr-mocking)\n  - [Done](#done)\n  - [Retries](#retries)\n  - [Timeout](#timeout)\n  - [Chai](#chai)\n- [Chaining](#chaining)\n- [Custom Plugins](#custom-plugins)\n- [TypeScript](#typescript)\n\n\u003c!-- tocstop --\u003e\n\nWhy\n===\n\nMocha out of the box often requires a lot of setup and teardown code in `beforeEach/afterEach` filters. Using this library, you can get rid of those entirely and build your tests declaratively by chaining functionality together. Using the builtin plugins and your own, you create bits of functionality and chain them together with a concise syntax. It will greatly reduce the amount of repetition in your codebase.\n\nIt should be compatible with other testing libraries as well (e.g. jest), but may require a couple small changes. If you're interested, try it out and let me know if it works.\n\nAs an example, here is what a test file might look like for an application setup with fancy-test. This chain could partially be stored to a variable for reuse.\n\n```js\ndescribe('api', () =\u003e {\n  fancy\n  // [custom plugin] initializes the db\n  .initDB({withUser: mockDBUser})\n\n  // [custom plugin] uses nock to mock out github API\n  .mockGithubAPI({user: mockGithubUser})\n\n  // [custom plugin] that calls the API of the app\n  .call('POST', '/api/user/foo', {id: mockDBUser.id})\n\n  // add adds to the context object\n  // fetch the newly created data from the API (can return a promise)\n  .add('user', ctx =\u003e ctx.db.fetchUserAsync(mockDBUser.id))\n\n  // do just runs arbitary code\n  // check to ensure the operation was successful\n  .do(ctx =\u003e expect(ctx.user.foo).to.equal('bar'))\n\n  // it is essentially mocha's it(expectation, callback)\n  // start the test and provide a description\n  .it('POST /api/user/foo updates the user')\n})\n```\n\nV3 Breaking Changes\n=====\n\nVersion 3 now uses `sinon` under the hood to manage stubs. Because of this stubs are now set like this:\n\n```js\nimport * as os from 'os'\n\ndescribe('stub tests', () =\u003e {\n  fancy\n  .stub(os, 'platform', stub =\u003e stub.returns('foobar'))\n  .it('sets os', () =\u003e {\n    expect(os.platform()).to.equal('foobar')\n  })\n})\n```\n\nUsage\n=====\n\nSetup is pretty easy, just install mocha and fancy-test, then you can use any of the examples below.\n\nAssume the following is before all the examples:\n\n```js\nimport {fancy} from 'fancy-test'\nimport {expect} from 'chai'\n```\n\nStub\n----\n\nStub any object. Like all fancy plugins, it ensures that it is reset to normal after the test runs.\n```js\nimport * as os from 'os'\n\ndescribe('stub tests', () =\u003e {\n  fancy\n  .stub(os, 'platform', stub =\u003e stub.returns('foobar'))\n  .it('sets os', () =\u003e {\n    expect(os.platform()).to.equal('foobar')\n  })\n})\n```\n\nCatch\n-----\n\ncatch errors in a declarative way. By default, ensures they are actually thrown as well.\n\n```js\ndescribe('catch tests', () =\u003e {\n  fancy\n  .do(() =\u003e { throw new Error('foobar') })\n  .catch(/foo/)\n  .it('uses regex')\n\n  fancy\n  .do(() =\u003e { throw new Error('foobar') })\n  .catch('foobar')\n  .it('uses string')\n\n  fancy\n  .do(() =\u003e { throw new Error('foobar') })\n  .catch(err =\u003e expect(err.message).to.match(/foo/))\n  .it('uses function')\n\n  fancy\n  // this would normally raise because there is no error being thrown\n  .catch('foobar', {raiseIfNotThrown: false})\n  .it('do not error if not thrown')\n})\n```\n\nWithout fancy, you could check an error like this:\n\n```js\nit('dont do this', () =\u003e {\n  try {\n    myfunc()\n  } catch (err) {\n    expect(err.message).to.match(/my custom errorr/)\n  }\n})\n```\n\nBut this has a common flaw, if the test does not error, the test will still pass. Chai and other assertion libraries have helpers for this, but they still end up with somewhat messy code.\n\nFinally\n-------\n\nRun a task even if the test errors out.\n\n```js\ndescribe('finally tests', () =\u003e {\n  fancy\n  .do(() =\u003e { throw new Error('x') })\n  .finally(() =\u003e { /* always called */ })\n  .end('always calls finally')\n})\n```\n\nNock\n----\n\nUses [nock](https://github.com/node-nock/nock) to mock out HTTP calls to external APIs. You'll need to also install nock in your `devDependencies`.\nAutomatically calls `done()` to ensure the calls were made and `cleanAll()` to remove any pending requests.\n\n```js\nconst fancy = require('fancy-test')\n\ndescribe('nock tests', () =\u003e {\n  fancy\n  .nock('https://api.github.com', api =\u003e api\n    .get('/me')\n    .reply(200, {name: 'jdxcode'})\n  )\n  .it('mocks http call to github', async () =\u003e {\n    const {body: user} = await HTTP.get('https://api.github.com/me')\n    expect(user).to.have.property('name', 'jdxcode')\n  })\n})\n```\n\nEnvironment Variables\n---------------------\n\nSometimes it's helpful to clear out environment variables before running tests or override them to something common.\n\n```js\ndescribe('env tests', () =\u003e {\n  fancy\n  .env({FOO: 'BAR'})\n  .it('mocks FOO', () =\u003e {\n    expect(process.env.FOO).to.equal('BAR')\n    expect(process.env).to.not.deep.equal({FOO: 'BAR'})\n  })\n\n  fancy\n  .env({FOO: 'BAR'}, {clear: true})\n  .it('clears all env vars', () =\u003e {\n    expect(process.env).to.deep.equal({FOO: 'BAR'})\n  })\n})\n```\n\nDo\n---\n\nRun some arbitrary code within the pipeline. Useful to create custom logic and debugging.\n\n```js\ndescribe('run', () =\u003e {\n  fancy\n  .stdout()\n  .do(() =\u003e console.log('foo'))\n  .do(({stdout}) =\u003e expect(stdout).to.equal('foo\\n'))\n  .it('runs this callback last', () =\u003e {\n    // test code\n  })\n\n  // add to context object\n  fancy\n  .add('a', () =\u003e 1)\n  .add('b', () =\u003e 2)\n  // context will be {a: 1, b: 2}\n  .it('does something with context', context =\u003e {\n    // test code\n  })\n})\n```\n\nAdd\n---\n\nSimilar to run, but extends the context object with a new property.\nCan return a promise or not.\n\n```js\ndescribe('add', () =\u003e {\n  fancy\n  .add('foo', () =\u003e 'foo')\n  .add('bar', () =\u003e Promise.resolve('bar'))\n  .do(ctx =\u003e expect(ctx).to.include({foo: 'foo', bar: 'bar'}))\n  .it('adds the properties')\n})\n```\n\nStdin Mocking\n-------------\n\nMocks stdin. You may have to pass a delay to have it wait a bit until it sends the event.\n\n```js\ndescribe('stdin test', () =\u003e {\n  fancy\n  .stdin('whoa there!\\n')\n  .stdout()\n  .it('mocks', () =\u003e {\n    process.stdin.setEncoding('utf8')\n    process.stdin.once('data', data =\u003e {\n      // data === 'whoa there!\\n'\n    })\n  })\n})\n```\n\nStdout/Stderr Mocking\n---------------------\n\nThis is used for tests that ensure that certain stdout/stderr messages are made.\nBy default this also trims the output from the screen. See the output by setting `TEST_OUTPUT=1`, or by setting `{print: true}` in the options passed.\n\nYou can use the library [stdout-stderr](https://npm.im/stdout-stderr) directly for doing this, but you have to be careful to always reset it after the tests run. We do that work for you so you don't have to worry about mocha's output being hidden.\n\n```js\ndescribe('stdmock tests', () =\u003e {\n  fancy\n  .stdout()\n  .it('mocks stdout', output =\u003e {\n    console.log('foobar')\n    expect(output.stdout).to.equal('foobar\\n')\n  })\n\n  fancy\n  .stderr()\n  .it('mocks stderr', output =\u003e {\n    console.error('foobar')\n    expect(output.stderr).to.equal('foobar\\n')\n  })\n\n  fancy\n  .stdout()\n  .stderr()\n  .it('mocks stdout and stderr', output =\u003e {\n    console.log('foo')\n    console.error('bar')\n    expect(output.stdout).to.equal('foo\\n')\n    expect(output.stderr).to.equal('bar\\n')\n  })\n})\n```\n\nDone\n----\n\nYou can get the mocha `done()` callback by passing in a second argument.\n\n```js\ndescribe('calls done', () =\u003e {\n  fancy\n  .it('expects FOO=bar', (_, done) =\u003e {\n    done()\n  })\n})\n```\n\nRetries\n-------\n\nRetry the test n times.\n\n```js\nlet count = 3\n\ndescribe('test retries', () =\u003e {\n  fancy\n  .retries(2)\n  .do(() =\u003e {\n    count--\n    if (count \u003e 0) throw new Error('x')\n  })\n  .it('retries 3 times')\n})\n```\n\nTimeout\n-------\n\nSet mocha timeout duration.\n\n```js\nconst wait = (ms = 10) =\u003e new Promise(resolve =\u003e setTimeout(resolve, ms))\n\ndescribe('timeout', () =\u003e {\n  fancy\n  .timeout(50)\n  .it('times out after 50ms', async () =\u003e {\n    await wait(100)\n  })\n})\n```\n\nChai\n----\n\nThis library includes [chai](https://npm.im/chai) for convenience:\n\n```js\nimport {expect, fancy} from 'fancy-test'\n\ndescribe('has chai', () =\u003e {\n  fancy\n  .env({FOO: 'BAR'})\n  .it('expects FOO=bar', () =\u003e {\n    expect(process.env.FOO).to.equal('BAR')\n  })\n})\n```\n\nChaining\n========\n\nEverything here is chainable. You can also store parts of a chain to re-use later on.\n\nFor example:\n\n```js\ndescribe('my suite', () =\u003e {\n  let setupDB = fancy\n                .do(() =\u003e setupDB())\n                .env({FOO: 'FOO'})\n\n  setupDB\n  .stdout()\n  .it('tests with stdout mocked', () =\u003e {\n    // test code\n  })\n\n  setupDB\n  .env({BAR: 'BAR'})\n  .it('also mocks the BAR environment variable', () =\u003e {\n    // test code\n  })\n})\n```\n\nUsing [do](#do) you can really maximize this ability. In fact, you don't even need to pass a callback to it if you prefer this syntax:\n\n```js\ndescribe('my suite', () =\u003e {\n  let setupDB = fancy\n                .do(() =\u003e setupDB())\n                .catch(/spurious db error/)\n                .do(() =\u003e setupDeps())\n\n  let testMyApp = testInfo =\u003e {\n    return setupDB.run()\n    .do(context =\u003e myApp(testInfo, context))\n  }\n\n  testMyApp({info: 'test run a'})\n  .it('tests a')\n\n  testMyApp({info: 'test run b'})\n  .it('tests b')\n})\n```\n\nCustom Plugins\n==============\n\nIt's easy to create your own plugins to extend fancy. In [oclif](https://github.com/oclif/oclif) we use fancy to create [custom command testers](https://github.com/oclif/example-multi-ts/blob/main/test/commands/hello.test.ts).\n\nHere is an example that creates a counter that could be used to label each test run. See the [actual test](test/base.test.ts) to see the TypeScript types needed.\n\n```js\nlet count = 0\n\nfancy = fancy\n.register('count', prefix =\u003e {\n  return {\n    run(ctx) {\n      ctx.count = ++count\n      ctx.testLabel = `${prefix}${count}`\n    }\n  }\n})\n\ndescribe('register', () =\u003e {\n  fancy\n  .count('test-')\n  .it('is test #1', context =\u003e {\n    expect(context.count).to.equal(1)\n    expect(context.testLabel).to.equal('test-1')\n  })\n\n  fancy\n  .count('test-')\n  .it('is test #2', context =\u003e {\n    expect(context.count).to.equal(2)\n    expect(context.testLabel).to.equal('test-2')\n  })\n})\n```\n\nTypeScript\n==========\n\nThis module is built in typescript and exports the typings. Doing something with dynamic chaining like this was [not easy](src/base.ts), but it should be fully typed throughout. Look at the internal plugins to get an idea of how to keep typings for your custom plugins.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foclif%2Ffancy-test","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Foclif%2Ffancy-test","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foclif%2Ffancy-test/lists"}