{"id":14967454,"url":"https://github.com/cades/respec-given","last_synced_at":"2025-10-25T19:31:01.882Z","repository":{"id":57144021,"uuid":"61027436","full_name":"cades/respec-given","owner":"cades","description":"rspec-given on mocha","archived":false,"fork":false,"pushed_at":"2017-12-08T06:05:30.000Z","size":2371,"stargazers_count":6,"open_issues_count":2,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-02-06T04:41:13.274Z","etag":null,"topics":["bdd","given","given-when-then","javascript","javascript-library","mocha","mochajs","rspec","tdd","test-driven-development","test-framework"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cades.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-06-13T10:31:33.000Z","updated_at":"2019-05-11T16:12:17.000Z","dependencies_parsed_at":"2022-09-06T00:11:40.515Z","dependency_job_id":null,"html_url":"https://github.com/cades/respec-given","commit_stats":null,"previous_names":[],"tags_count":18,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cades%2Frespec-given","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cades%2Frespec-given/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cades%2Frespec-given/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cades%2Frespec-given/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cades","download_url":"https://codeload.github.com/cades/respec-given/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":238018671,"owners_count":19402757,"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":["bdd","given","given-when-then","javascript","javascript-library","mocha","mochajs","rspec","tdd","test-driven-development","test-framework"],"created_at":"2024-09-24T13:38:04.970Z","updated_at":"2025-10-25T19:31:01.137Z","avatar_url":"https://github.com/cades.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n  \u003cimg src=\"https://raw.githubusercontent.com/cades/respec-given/master/doc/logo.png\" width=\"200\" height=\"200\" alt=\"respec-given test framework extension\"/\u003e\n\n  \u003cp\u003e\n    \u003ca href=\"https://npmjs.com/package/respec-given\"\u003e\n      \u003cimg src=\"https://img.shields.io/npm/v/respec-given.svg\" alt=\"npm version\" /\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://travis-ci.org/cades/respec-given\"\u003e\n      \u003cimg src=\"https://travis-ci.org/cades/respec-given.svg?branch=master\" alt=\"Build Status\" /\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://npmjs.com/package/respec-given\"\u003e\n      \u003cimg src=\"https://img.shields.io/npm/dm/respec-given.svg\"\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://app.fossa.io/projects/git%2Bgithub.com%2Fcades%2Frespec-given?ref=badge_shield\" alt=\"FOSSA Status\"\u003e\n      \u003cimg src=\"https://app.fossa.io/api/projects/git%2Bgithub.com%2Fcades%2Frespec-given.svg?type=shield\"/\u003e\n    \u003c/a\u003e\n  \u003c/p\u003e\n\u003c/div\u003e\n\n# respec-given\n\n[rspec-given](https://github.com/jimweirich/rspec-given) in JavaScript, on top of [mocha](https://mochajs.org/).\n\n\n* encourages cleaner, readable, and maintainable tests using `Given`/`When`/`Then` API\n* test async code without boilerplate code. support callback/Promise/generator/Observable/async function\n* descriptive assertion message\n\n\n## Demo\n\n![usage demo](https://raw.githubusercontent.com/cades/respec-given/master/doc/demo.gif)\n\n\n## Installation\n\ninstall `respec-given` locally\n\n    npm install --save-dev respec-given\n\n## Usage\n\n### Node.js\n\nwith `mocha` command:\n\n    mocha --ui respec-given --require respec-given/na-loader\n\nsee more about [natural assertion loaders](#transform-test-code)\n\n\n### Browser\n\nadd script tag after load mocha:\n\n    \u003cscript src=\"node_modules/respec-given/mocha/browser.js\"\u003e\u003c/script\u003e\n    \u003cscript\u003emocha.setup('respec-given')\u003c/script\u003e\n\nnatural assertion transformation tools is not available yet, but you won't wait too long :)\n\n## Example\n\nHere is a spec written in respec-given.\n\n```js\nconst Stack = require('../stack');\n\ndescribe('Stack', () =\u003e {\n\n  const stack_with = (initial_contents) =\u003e {\n    const stack = Object.create(Stack);\n    initial_contents.forEach(item =\u003e stack.push(item))\n    return stack;\n  };\n\n  Given('stack', $ =\u003e stack_with($.initial_contents))\n  Invariant($ =\u003e $.stack.empty() === ($.stack.depth() === 0))\n\n  context(\"with no items\", () =\u003e {\n    Given('initial_contents', () =\u003e [])\n    Then($ =\u003e $.stack.depth() === 0)\n\n    context(\"when pushing\", =\u003e {\n      When($ =\u003e $.stack.push('an_item'))\n\n      Then($ =\u003e $.stack.depth() === 1)\n      Then($ =\u003e $.stack.top() === 'an_item')\n    })\n  })\n\n  context(\"with one item\", () =\u003e {\n    Given('initial_contents', () =\u003e ['an_item'])\n\n    context(\"when popping\", () =\u003e {\n      When('pop_result', $ =\u003e $.stack.pop())\n\n      Then($ =\u003e $.pop_result === 'an_item')\n      Then($ =\u003e $.stack.depth() === 0)\n    })\n  })\n\n  context(\"with several items\", () =\u003e {\n    Given('initial_contents', () =\u003e ['second_item', 'top_item'])\n    GivenI('original_depth', $ =\u003e $.stack.depth())\n\n    context(\"when pushing\", () =\u003e {\n      When($ =\u003e $.stack.push('new_item'))\n\n      Then($ =\u003e $.stack.top() === 'new_item')\n      Then($ =\u003e $.stack.depth() === $.original_depth + 1)\n    })\n\n    context(\"when popping\", () =\u003e {\n      When('pop_result', $ =\u003e $.stack.pop())\n\n      Then($ =\u003e $.pop_result === 'top_item')\n      Then($ =\u003e $.stack.top() === 'second_item')\n      Then($ =\u003e $.stack.depth() === $.original_depth - 1)\n    })\n  })\n```\n\nBefore we take a closer look at each statement used in respec-given, I hope you can read rspec-given's [documentation](https://github.com/jimweirich/rspec-given#given) first. It explained **the idea behind its design** excellently.\n\nInstead of repeat Jim's words, I'll simply introduce API here.\n\n\n### Given\n\n`Given` is used to setup precondition. There are 2 types of Given: lazy Given and immediate Given.\n\n#### Lazy Given\n\n```js\n    // Given(\"varname\", fn)\n    Given('stack', () =\u003e stack_with([]))\n```\n\n`$.stack` become accessible in other clauses.\n\nThe function will be evaluated until first access to `$.stack`. Once the function is evaluated, the result is cached.\n\nIf you have multiple `Given`s, like\n\n```js\n    Given('stack1', () =\u003e stack_with([1, 2]))\n    Given('stack2', () =\u003e stack_with([3, 4]))\n```\n\nyou can use object notation as a shorthand:\n\n```js\n    // Given({hash})\n    Given({\n      stack1: () =\u003e stack_with([1, 2]),\n      stack2: () =\u003e stack_with([3, 4])\n    })\n```\n\n#### Immediate Given\n\nThere are several form of immediate Given. The first category is `Given(fn)`. Since it doesn't specify a variable name, it is for side-effects. The Second category is `GivenI(varname, fn)`. It will evaluate `fn` immediately and assign the returned result to `this.varname`. If error occurs in `fn`, `GivenI` expression will fail.\n\n```js\n    // Given(fn)\n    Given($ =\u003e stack_with([]))\n```\n\n  Using this form, the function will be evaluated **immediately**.\n\n```js\n    Given(() =\u003e new Promise(...))\n```\n\n  If the function returns a Promise, next statement will be executed until the promise is resolved.\n\n```js\n    Given(() =\u003e new Observable(...))\n```\n\n  If the function returns an Observable, next statement will be executed until the observable is complete.\n\n```js\n    // Given(fn(done))\n    Given(($, done) =\u003e {\n      asyncOp(done)\n    })\n```\n\n  Using this form, you can perform an asynchronous operation. When finished, you should call `done()` is success, or `done(err)` if the operation failed.\n\n```js\n    // Given(generatorFn)\n    Given(function*() { yield yieldable })\n```\n\n  generator function is also supported. It will be executed until it returns or throws.\n\n```js\n    // GivenI(\"varname\", fn)\n    GivenI('stack', () =\u003e stack_with([]))\n```\n\n  Using this form, the function will be evaluated **immediately** and the return value is assigned to `$.stack`.\n\nNote unlike lazy-Given, if `fn` returns a Promise or an Observable, it will be resolved/completed automatically.\n\nAlso, `fn` can have a callback with signature `(err, res)`, so you can perform asynchronous operation.\n\n`fn` can also be a generator function. the returned value will be assigned to `$.varname`.\n\n\n#### Let statement\n\nrspec has [`let`](http://www.relishapp.com/rspec/rspec-core/v/3-4/docs/helper-methods/let-and-let) helper, which is a lazy variable declaration. In rspec, `Given` is simply alias to `let`. (Jim mentioned a [reason](https://github.com/jimweirich/rspec-given/wiki/Using-let-and-given-together) why we need `let` if we have `Given` already.)\n\nSince ES6 introduce `let` keyword, to avoid name collision, respec-given choose capitalized `Let`.\n\n- `Let` is an alias to `Given`, which maps to rspec-given's `let/Given`\n- `LetI` is an alias to `GivenI`, which maps to rspec-given's `let!/Given!`\n\n\n### When\n\n`When` is used to perform action and capture result (or Error). All asynchronous operation should be performed here.\n\n```js\n    // When(fn)\n    When($ =\u003e $.stack.pop())\n```\n\n  this function will be executed immediately.\n\n```js\n    When(() =\u003e new Promise(...))\n```\n\n  If the function returns a Promise, next statement will be executed until the promise is resolved.\n\n```js\n    When(() =\u003e new Observable(...))\n```\n\n  If the function returns an Observable, next statement will be executed until the observable is complete.\n\n```js\n    // When(fn($, done))\n    When(($, done) =\u003e {\n      asyncOp(function(err, res) {\n        done(err, res)\n      })\n    })\n```\n\n  Using this form, you can perform an asynchronous operation. When finished, you should call `done()` is success, or `done(err)` if the operation failed.\n\n```js\n    // When(generatorFn)\n    When(function*() { yield yieldable })\n```\n\n  generator function is also supported. It will be executed until it returns or throws.\n\n```js\n    // When(\"result\", fn)\n    When('pop_result', $ =\u003e $.stack.pop())\n    Then($ =\u003e $.pop_result === 'top_item')\n```\n\n  Using this form, the function will be executed immediately and the return value is assigned to `$.pop_result`.\n\n```js\n    When('result1', () =\u003e Promise.resolve(1))\n    When('result2', () =\u003e Promise.reject(2))\n    Then($ =\u003e $.result1 === 1)\n    Then($ =\u003e $.result2 === 2)\n```\n\n  If the function return a Promise, the promise will be resolved to a value (or an error) first, then assign the resolved value to `$.result`.\n  \n```js\n    When('result', () =\u003e throw new Error('oops!'))\n    Then($ =\u003e $.result.message === 'oops')\n```\n\n  If the function throws an error synchronously, the error will be caught and assigned to `$.result`.\n\n```js\n    // When(\"result\", fn($, done))\n    When('result', ($, done) =\u003e {\n      asyncOp(function(err, res) {\n        done(err, res)\n      })\n    })\n```\n\n  Using this form, you can perform asynchronous operation here.\n  If operation succeed, you should call `done(null, res)`, and `res` will be assigned to `$.result`.\n  If operation failed, you should call `done(err)`, and `err` will be assigned to `$.result`.\n  \n  If the function throws an error **synchronously**, the error will be caught and assigned to `$.result`.\n\n```js\n    // When(\"result\", generatorFn)\n    When('result', function*() { return yield yieldable })\n```\n\n  generator function is also supported. It will be executed until it returns or throws. The value it returns or throws will be assigned to `$.result`.\n\nIf you have multiple `When`s, like\n\n```js\n    When('result1', () =\u003e stack1.pop())\n    When('result2', () =\u003e stack2.pop())\n```\n\nyou can use object notation as a shorthand:\n\n```js\n    // When({hash})\n    When({\n      result1: () =\u003e stack1.pop()),\n      result2: () =\u003e stack2.pop())\n    })\n```\n\n### Then\n\nA *Then* clause forms a mocha test case of a test suite, it is like `it` in classical BDD style mocha test. Note that:\n\n1. *Then* should only contain an assertion expression\n2. *Then* should not have any side effects.\n3. *Then* only support synchronous operation. all asynchronous operation should be done in *When* clause.\n\nLet me quote [Jim's words](https://github.com/jimweirich/rspec-given#then) here:\n\n\u003e Let me repeat that: **Then clauses should not have any side effects!** Then clauses with side effects are erroneous. Then clauses need to be idempotent, so that running them once, twice, a hundred times, or never does not change the state of the program. (The same is true of And and Invariant clauses).\n\nOK, let's see some example!\n\n```js\n    // Then(fn)\n    Then($ =\u003e expect($.result).to.be(1))\n```\n\nThis form uses a 3rd-party assertion/matcher library, for example, chai.js.\n\n```js\n    Then($ =\u003e $.result === 1)\n```\n\nThis form returns a boolean expression, this is called [*natural assertion*](#natural-assertion). if the function returns a **boolean false**, this test is considered fail.\n\n\n### And\n\nplease refer to [rspec-given's documentation](https://github.com/jimweirich/rspec-given#and)\n\n\n### Invariant\n\nplease refer to [rspec-given's documentation](https://github.com/jimweirich/rspec-given#invariant)\n\n\n## Execution Ordering\n\nplease refer to [rspec-given's documentation](https://github.com/jimweirich/rspec-given#execution-ordering)\n\n\n## \u003ca name=\"natural-assertion\"\u003e\u003c/a\u003eNatural Assertions\n\nrespec-given supports \"natural assertions\" in *Then*, *And*, and *Invariant* blocks. Natural assertions are just boolean expressions, without additional assertion library.\n\n### Failure Messages with Natural Assertions\n\nThere are 2 kind of failure message, depends on whether test code is transformed.\n\nIf the test code is not transformed, simple failure message applies. Otherwise, comprehensive failure message applies. The former simply points out which expression failed, the later show each subexpression's value, which is easier for developers to debug.\n\n#### Simple Failure Message\n\nexample:\n\n```\n     Error: Then { $.stack.depth() === 0 }\n\n       Invariant expression failed.\n       Failing expression: Invariant { $.stack.empty() === ($.stack.depth() === 0) }\n\n```\n\n#### Comprehensive Failure Message\n\nexample:\n\n```\n  Error: Then { $.stack.depth() === 0 }\n\n  Invariant expression failed at test/stack_spec.coffee:23:13\n\n         Invariant { $.stack.empty() === ($.stack.depth() === 0) }\n                       |     |    |  |      |     |    |  |\n                       |     |    |  |      |     |    0  true\n                       |     |    |  |      |     #function#\n                       |     |    |  false  Object{_arr:[]}\n                       |     |    false\n                       |     #function#\n                       Object{_arr:[]}\n\n         expected: false\n         to equal: true\n```\n\n### Checking for Errors with Natural Assertions\n\nIf you wish to see if the result of a When clause is an Error, you can use the following:\n\n    When('result', () =\u003e badAction())\n    Then($ =\u003e Failure(CustomError, /message/).matches($.result))\n    Then($ =\u003e Failure(CustomError).matches($.result))\n    Then($ =\u003e Failure(/message/).matches($.result))\n\n\n### \u003ca name=\"transform-test-code\"\u003e\u003c/a\u003eTransform test code\n\n#### What is a *natural assertion loader*?\n\nNatural assertion loader is a tool which analysis test code's `Then` expression, gather context information, and generate code that carries these information. When assertion failed (return false), these information are used to evaluate failed Then clause's subexpression and generate diagnosis message for you.\n\n#### Why tooling?\n\nBecause in JavaScript, lexical binding can not be \"captured\" during execution time. Lexical binding is resolved at lex time, it's the world view of specific block of code. You have no way to share this view to others (in JavaScript). For example:\n\n```js\n    const x = 1\n    Then(() =\u003e x === 0)\n```\n\n`Then` received a function, which returns `false`. Even `Then` can know `x`'s existence by analysis `fn.toString()`, `Then` have no way to access `x`. No.\n\nThis is a meta-programming problem, which can not be solved in JavaScript itself. That's why we need a loader (preprocessor, transpiler, instrumenter, whatever you like to call it).\n\n#### When do I need it?\n\nWhen you use natural assertion, transformed test code would generate more helpful error message for you.\n\nOn the other hand, if you are using assertion library (like node.js built-in `assert`, `chai.js`, `expect.js`, or `shouldjs`), which provide their diagnosis message already, then you don't need natural assertion loader.\n\n#### Ok. Tell me how to use it.\n\nthere are 3 Node.js loader out of the box:\n\n- JavaScript loader\n\n    ```bash\n    mocha --ui respec-given --require respec-given/na-loader\n    ```\n    \n- CoffeeScript loader\n\n    ```bash\n    mocha --ui respec-given --require respec-given/na-loader/coffee\n    ```\n\n- LiveScript loader\n\n    ```bash\n    mocha --ui respec-given --require respec-given/na-loader/ls\n    ```\n\n## Acknowledgments\n\n* [How to Stop Hating your Test Suite](https://youtu.be/VD51AkG8EZw?t=8m42s) for blowing my mind;\n* [rspec-given](https://github.com/jimweirich/rspec-given) for inspiration;\n* [mocha-gwt](https://github.com/TheLudd/mocha-gwt), [mocha-given](https://github.com/rendro/mocha-given), [jasmine-given](https://github.com/searls/jasmine-given), [given.js](https://github.com/freshtonic/given.js), [bdd-lazy-var](https://github.com/stalniy/bdd-lazy-var) for JS implementation reference.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcades%2Frespec-given","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcades%2Frespec-given","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcades%2Frespec-given/lists"}