{"id":22763608,"url":"https://github.com/mearns/js-test-framework","last_synced_at":"2025-03-30T09:42:17.902Z","repository":{"id":78409663,"uuid":"84508399","full_name":"mearns/js-test-framework","owner":"mearns","description":null,"archived":false,"fork":false,"pushed_at":"2018-08-21T12:59:11.000Z","size":30,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-02-05T11:45:04.766Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"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/mearns.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-03-10T02:02:09.000Z","updated_at":"2017-03-10T02:02:47.000Z","dependencies_parsed_at":"2023-03-02T01:30:17.105Z","dependency_job_id":null,"html_url":"https://github.com/mearns/js-test-framework","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mearns%2Fjs-test-framework","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mearns%2Fjs-test-framework/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mearns%2Fjs-test-framework/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mearns%2Fjs-test-framework/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mearns","download_url":"https://codeload.github.com/mearns/js-test-framework/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246301955,"owners_count":20755512,"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":[],"created_at":"2024-12-11T11:09:21.717Z","updated_at":"2025-03-30T09:42:17.884Z","avatar_url":"https://github.com/mearns.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"## Examples\n\n```javascript\n\nimport {regarding} from 'js-test-framework';\nimport {expect} from 'chai';    // or any other assertion library, or nothing at all.\n\nimport * as myModule from './myModule';\n\nregarding(myModule, (regardingMyModule) =\u003e {\n    regardingMyModule.andSpecifically(regardingMyModule.subject.frob, (regardingFrobFunction) =\u003e {\n        regardingFrobFunction.testThat(({oneMay, when, given}, frob) =\u003e {\n            oneMay((it, {expectedOutput}) =\u003e {\n                expect(it.value).to.equal(expectedOutput);\n            })\n            when(({input}) =\u003e frob(input));\n            given({input: X, expectedOutput: Y});\n        });\n\n        regardingFrobFunction.testThat(({oneMay, when, given}, frob) =\u003e {\n            oneMay((it) =\u003e {\n                expect(result.threw).to.be.an.instanceOf(Error);\n            });\n            when(({input}) =\u003e frob(input));\n            given({input: Z});\n        });\n    });\n});\n```\n\nThe order in which the functions of the test spec (passed to `testThat`) is recommended\nto be reversed from typical style. The typical style is \"given...when...then\", also known as\n\"arrange...act...assert\". This is a natural way to write code: you setup your environment,\nthen perform some action, then make assertions about what happened. But it's not really\na natural way to think about tests.\n\nInstead, we recommend writing your tests in reverse order, which is why we had to change\nsome of the names ;-). In this framework, tests should be written as\n\"OneMay \\[expect that\\] ..., When ..., Given \\[that\\] ...\". This isn't simply the order in\nwhich the code should occur in the file, it's the recommended order for actually writing\n(and thinking about) your tests: decide what state it is that you actually want to assert on,\nthen figure out what actions you need to take to bring that state about, and lastly determine\nwhat needs to happen to setup your actions (which is largely incidental to the test).\n\nYou aren't obligated to write your code in this order: all your \"givens\" will be run\nfirst, then your \"when\", and lastly your \"oneMay\"'s, regardless of what order they\nappear in the code. This is simply recommended as a best practice. Also note that the\norder in which your \"givens\" occur is the same order in which they will be applied to\nsetup the test, and likewise the order of your \"oneMay\"'s is the order in which they\nwill be applied to assert on your test.\n\n### API Documentation\n\n### `regarding([description], subject, spec)`\n\nCreates a new context for running tests in. Contexts can be arbitarily nested.\nThis is similar to a \"describe\" in mocha.\n\nThe `spec` is a function that will be invoked _to define_ test cases in this context.\nThis is done unconditionally (though not necessarily syncronously or immediately),\neven if test case evaluation is conditional (e.g., only run tests cases matching a\ncertain pattern).\n\nWhen `spec` is invoked, it is invoked with a single argument, generally referred to\nas the _context API_. This object includes the necessary functions for defining sub-contexts\nand test cases within the context created by the call to `regarding`. It also includes\nfunctions for setting up and tearing down tests. Lastly, it includes a `subject` property\nwhich is set to the value of the `subject` parameter passed to `regarding`.\n\nIt is recommended to _not_ unpack this parameter, and to name it something specific to\nthe context. This will avoid having the same name reference different variables within\nnested scopes. The suggested pattern is something like `regarding${Subject}` so that\nyour statements utilizing it read somewhat fluently.\n\nIf `spec` returns a thenable, then the specification is not considered defined until\nthe thenable fulfills. The specific fulfill value is ignored, but this can be used\nto define sub-contexts and test cases asynchronously using the provided API objects.\n\n### `ContextAPI::andSpecifically([description], subject, spec)`\n\nThis is the same as the top-level `regarding`, except that it creates a nested context.\n\n### `ContextAPI::given(setup)`\n\nThis is the \"before-each\" setup hook for the context. It works similarly to `TestAPI::given()`,\nexcept that the setup is performed for every test case defined in the context _and_ in nested\ncontexts.\n\nNote that it is generally prefrable to limit the amount of setup done in a context, pushing as\nmuch as possible down into the test cases. Consider this function a crutch. The recommended\nway to do common setup is to define common setup functions at an appropriate scope,\nand simply have the test cases call `TestAPI::given` with that function is they want to use\nthe same setup. Performing common setups in a context makes it harder to keep track of exactly\nwhat setups apply in each test case, and hard or impossible for individual test-cases to pick-and-choose\nwhich pieces of setup they want.\n\nReturns an object with a single method, `taken(cleanup)`, used for cleanup/teardown.\nThis method is passed a cleanup function which is called after a test case\nis evaluated, regardless of whether the test passed, failed, or erred. The cleanup\nfunction is invoked with an object containing the test variables as were defined\nat the time the corresponding setup function completed. If subsequent setups\n_replaced_ the value of a test variable, the replacements will be rolled back\nby the time then corresponding cleanup function is called.\n\nAs a **best practice**, you should avoid mutation of test variables: subsequent\nsetup functions can result in _replacement_ of test variables, but should not mutate\nthe existing objects. If mutation occurs, then the test variables passed to the cleanup\nfunction will be different than those that came out of the setup function.\n\n#### `ContextAPI::given.pure(setup)`\n\nThe `ContextAPI::given` function has a `pure` method attached to it, which marks the given\n`setup` as a pure setup. Otherwise, passing a `setup` (either a function, an Object, or a\npromise of an Object) is identical to passing it directly to `given`.\n\nA **pure** setup is one which _has no side effects_ and whose result depends _only_ on the\nexplicit inputs. An Object setup is implicitly a pure setup, but neither a setup function\nnor a setup promise are implicitly pure and must be marked as such if you know that they are.\n\nA pure setup can be invoked at any time, and one or more times, and will always return the\nsame value. By marking a setup as pure, the test runner can take advantage of this by running it\nonce for all tests that use it and simply caching the result. It is unspecified whether or not\na pure setup will be run if there are no test cases that rely on it, but since it is a pure function,\nit doesn't matter.\n\nMarking setup and other test steps as pure can greatly improve the speed of the run, because the\npure steps can be run once and reused many times, and they can be run in parallel. However, you\n**must make sure that the functions are really pure** or you will run into any number of race\nconditions and your tests will be generally meaningless and unpredicatable.\n\n### `ContextAPI::testThat(description, testFunc)`\n\nThis is how you actually define a test case within a context.\n\nThe `testFunc` is a function that will be invoked _to define_ the test case. Similar to\nthe `spec` function passed to `regarding`, this is invoked unconditionally during test\ndefinition (though not necessarily synchronously or immediately). _If and when_ the test\ncase is actually evaluated, any functions passed to `given`, `when`, or `oneMay` will be invoked,\nas described below.\n\n#### `testFunc(testAPI, subject)`\n\nThe `testFunc` is invoked with two arguments as shown above. One is the test API which\nprovides functions for setting up and running the test case. The second is the subject\nof _the context_ in which the test is run (i.e., `ContextAPI::subject` for the closest\ncontaining context).\n\nSince test cases are never nested, it is fine to unpack the `testAPI` into the individual\nAPI functions that you need.\n\n### `ContextAPI::tag(tag, [tag, [...]])`\n\nUsed to tag subsequent tests and sub-contexts with the specified labels. When running tests,\nyou can select which test cases to run based on the applied tags. Tags applied to a context\napply to every test case defined inside it, _or_ inside a sub-context.\n\nThe use of this function looks like:\n\n```javascript\nregardingMyModule.tag('foo', 'bar').testThat(...);\n```\n\nSpecifically, the `tag` function does not alter the state of the `ContextAPI` on which it\nis invoked (at least no in any meaningful way), but instead returns an object which has the same\ninterface as `ContextAPI` but will attach the specified tags to any sub contexts and test cases\ndefined with it.\n\n### `ContextAPI::doOnce(setup)`\n\n```javascript\ndoOnce((Object) =\u003e Object);\ndoOnce((Object) =\u003e Promise\u003cObject\u003e);\n```\n\nA setup method that ensures the given setup is performed at most once, and only\nif a test case within the context is actually to be evaluated. This will be treated\nas a test step in a test case evaluation, in the correct sequence in which the\n`doneOnce` function is invoked relative to any `given` function invocations, however,\nthe step will have state associated with it such that once it has been invoked once,\nit will become a no-op for any future test-cases it may be attached to.\n\nLike the setup function passed to `given`, it will be invoked with an object containing\nthe current test variables, and any non-null Object that comes out of the setup function\nwill be used to update the test variables. Since the setup function is only called once,\nthe resulting object is cached for use in future test cases.\n\nThis is another crutch, much like using `given` at the context level instead of inside\na test case, but even worse. Ideally, you should be doing any requisite setup and teardown\nfor every test case. But sometimes, things just aren't that simple.\n\nThis is _not_ recommended as an alternative to using `pure`, this is intended as a way\nto perform side-effects that are not idempotent.\n\nAs with `given`, if the function throws, the step fails, and _any_ test cases associated with\nit will be marked as failing in setup. Likewise if the setup function returns a thenable that\nrejects or timesout. Returned thenables that fulfill are handled as with `given`.\n\nThe `doOnce` function returns an object that has an `undo` method attached to it, which\ntakes a `cleanup` function as a parameter and shouldbe used as a corresponding tear-down.\nThe cleanup function will be used for tear down _only_ once, corresponding to the single\ninvocation of the setup function. For subsequent test cases, the cleanup function is ignored\nand normal unwinding of test-variables is performed.\n\n### `TestAPI::given(setup)`\n\n```javascript\ngiven(Object);\ngiven(Promise\u003cObject\u003e);\ngiven((Object) =\u003e Object);\ngiven((Object) =\u003e Promise\u003cObject\u003e);\n```\n\nThe `given` function is used to setup the scenario of the test case. It has two main purposes:\ndefine test variables and prepare requisite systems for the test. The end result of all the\ncalls to `given` is an object which will be passed in to the actual test function as a way\nof providing test variables. Additionally, debugging information for each test can provide\ninformation about this object to provide context for understanding a test's results.\n\nTo set up this object of test variables, you can pass in an Object, or a thennable that\nfulfills with an Object. In either case, the Object will be merged into the previous\ntest-variable Object, created by previous calls to `given` (initialized as an empty\nObject before any `givens` have been called).\n\nAlternatively, you can pass in a function that _returns_ an Object, or a promise of an\nObject. The returned/fulfilled Object will likewise be merged into the existing test-variable\nObject. Also note that the existing test-variables will be passed in as an Object as the only\nparameter to the provided function so you can use that to make decisions about how to populate\nthe returned object if necessary. Note however that _changing_ the provided Object has\n_undefined behavior_: specifically, it is unspecified whether or not this is the actual\nobject that will be used, so changes made to this object _may or may not_ affect the resulting\ntest variables. In other words, don't mutate the passed in object.\n\nAlso note that the top-level fields of these Objects are considered to define the test variables,\nand only a shallow-merge is done. If you return (or provide) an Object with a property that\nalready exists as a test variable, the value of that property will _be the new value_ of that\ntest variable, the values will not themselves be merged.\n\nPassing a function is also a way to perform necessary side effects for setup, e.g., configuring\nother systems that the test will rely on. However, it _important to note_ that the provided\nfunctions are _not_ invoked synchronously or immediately. In fact, they will _only_ be invoked\n_if_ the test case is actually evaluated. They are guaranteed to be executed in the same\norder in which they were provided to `given`, and if any function returns a thenable, subsequent\ngivens will _not_ be evaluated until it fulfills.\n\nYou need to be careful about invoking functions _in_ or _around_ calls to _given_, cognizant that such\ninvocations are synchronous as part of the _test definition_, where as functions passed to _given_ etc.\nare executed asynchronously and only when the test case is to be evaluated. For instance, it is recommended\nthat a function under test _not_ be executed during _test definition_, and requisite systems _not_ be\nconfigured during _test definition_: both of these things should be encapsulated inside functions\npassed to `given` and `when` so that they only occur as part of test evaluation.\n\nIf a provided function returns a `null` or `undefined` value, the value is ignored. Similarly, if\na function returns a promise that fulfills with `null` or `undefined`, the value is ignored. In both\ncases, the test variables are left unchanged.\n\nOn the other hand, if a function returns a non-thennable value that is not of type \"object\" or \"undefined\",\nor if the function returns a promise that fulfills with such a value, the test-case will be marked as\nfailing setup. Likewise, if a function returns a promise that rejects or timesout, the test-case will\nfail.\n\nUsing an object is definitely a simple solution for defining test variables, but you need to be careful\nabout leaking references to this variable that could allow it to be modified. Likewise with the values\nof the variables it defines. Using Object literals without assigning them to variables will aid in this.\nNote that have a function that returns an Object that is otherwise accessible will not solve the problem.\nThe goal is to make sure that test variables are never modified or mutated outside of a `setup` step.\n\nAs with `ContextAPI::given`, the `TestAPI::given` has an attached `pure` method that can be used\nto mark the setup step as a pure step. It also retuns an object that has a `taken` method for cleanup.\n\n### `TestAPI::when(exerciser)`\n\n```javascript\nwhen((Object) =\u003e anything);\nwhen((Object) =\u003e Promise\u003canything\u003e);\n```\n\nThis is where you define what actions you're actually testing. E.g., you would typically invoke your function\nunder test here.\n\nPass a function that will exercise your unit under test or perform whatever other actions you want to test.\nAny value returned other than a thennable will be captured as the result value, which can subsequently be\ninspected and asserted on in calls to `oneMay`. If the provided function returns a thennable, then whatever\nvalue it fulfills with will be used as the result value.\n\nIf your unit under test is expected to return a thennable and you want to actually inspect the thennable, as opposed\nto the settled state of the thennable, you'll need to wrap it inside an Object before returning it from the\nprovided function.\n\nNote that if the provided function fails, or a returned thennable rejects, the test is _not automatically failed_.\nA result object is created that encapsulates the results of your function, whether it failed or not. This is\nthe primary conceptual difference between `given` and `when` (functionally they have different signatures as well).\n\nThe result object is the one that is passed\nto your `oneMay` inspectors. This provides an easy way to test failure cases. Similarly, if the thennable returned\nby your `when` function rejects, the test is not automatically failed, but the information about the rejection is\ncaptured in the result object for inspection.\n\nYou can only call `when` _once_ per test case. Calling it more than once will result in an error _during test definition_.\n\nLike `TestAPI::given`, this function has an attached `pure` method that can be used to mark the step as pure.\n\n### `TestAPI::oneMay(inspector)`\n\n```javascript\noneMay((result, Object) =\u003e anything);\noneMay((result, Object) =\u003e Promise\u003canything\u003e);\n```\n\nThis is where you inspect the results of your `when` function and decide if your test passed or failed.\n\nThe provided function will be invoked during test evaluation _after_ all test setup and _after_ your exerciser (if any)\nis settled. To fail the test, throw an Error from within your `inpsector` function, or return a thennable that rejects.\nIf the return thennable timesout according to a preconfigured max timeout, the test will also fail.\n\nYour `inspector` is invoked with two arguments: the first is the result object generated by your `when` exerciser;\nthe second is the object of test variables setup for your test case. If not `when` is used for the test case,\nthen the first argument, `result`, will be null. If no `given`'s are used to prepare test variables, then\nthe second argument will be an empty object.\n\nThe fulfillment value of a returned thenable doesn't matter and will be ignored.\n\nYou can have as many `oneMay`'s in you test as you like, they will be run in sequence (unless marked as _pure_), and if\nany one results in a failure as described above, then the entire test case fails. It is generally preferable to have\na small number of `oneMay`'s in each test case, and use multiple test cases to test multiple things.\n\nLike `given` and `when`, this function has an attached `pure` method that is used to mark it as a pure step.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmearns%2Fjs-test-framework","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmearns%2Fjs-test-framework","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmearns%2Fjs-test-framework/lists"}