{"id":17255561,"url":"https://github.com/ffmathy/fluffyspoon.javascript.testing.faking","last_synced_at":"2025-04-12T16:38:51.817Z","repository":{"id":33118862,"uuid":"139153585","full_name":"ffMathy/FluffySpoon.JavaScript.Testing.Faking","owner":"ffMathy","description":"An NSubstitute port to TypeScript called substitute.js.","archived":false,"fork":false,"pushed_at":"2024-03-21T21:22:28.000Z","size":963,"stargazers_count":206,"open_issues_count":7,"forks_count":22,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-04-12T10:42:31.353Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"TypeScript","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/ffMathy.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","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},"funding":{"github":"ffMathy","patreon":null,"open_collective":"substitute-js","ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"custom":null}},"created_at":"2018-06-29T13:36:07.000Z","updated_at":"2025-03-30T00:44:48.000Z","dependencies_parsed_at":"2023-02-16T15:40:48.669Z","dependency_job_id":"55cf425d-45ef-41d0-982c-b240713a4aeb","html_url":"https://github.com/ffMathy/FluffySpoon.JavaScript.Testing.Faking","commit_stats":{"total_commits":242,"total_committers":24,"mean_commits":"10.083333333333334","dds":0.6074380165289256,"last_synced_commit":"30c69d9586fd6ccc3affa1bdab9c956d7c679715"},"previous_names":[],"tags_count":97,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ffMathy%2FFluffySpoon.JavaScript.Testing.Faking","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ffMathy%2FFluffySpoon.JavaScript.Testing.Faking/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ffMathy%2FFluffySpoon.JavaScript.Testing.Faking/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ffMathy%2FFluffySpoon.JavaScript.Testing.Faking/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ffMathy","download_url":"https://codeload.github.com/ffMathy/FluffySpoon.JavaScript.Testing.Faking/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248597921,"owners_count":21130979,"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-10-15T07:12:03.011Z","updated_at":"2025-04-12T16:38:51.796Z","avatar_url":"https://github.com/ffMathy.png","language":"TypeScript","funding_links":["https://github.com/sponsors/ffMathy","https://opencollective.com/substitute-js","https://opencollective.com/substitute-js/contribute","https://opencollective.com/substitute-js/organization/0/website","https://opencollective.com/substitute-js/organization/1/website","https://opencollective.com/substitute-js/organization/2/website","https://opencollective.com/substitute-js/organization/3/website","https://opencollective.com/substitute-js/organization/4/website","https://opencollective.com/substitute-js/organization/5/website","https://opencollective.com/substitute-js/organization/6/website","https://opencollective.com/substitute-js/organization/7/website","https://opencollective.com/substitute-js/organization/8/website","https://opencollective.com/substitute-js/organization/9/website"],"categories":[],"sub_categories":[],"readme":"\u003ca href=\"https://opencollective.com/substitute-js#section-contribute\"\u003e\u003ca href=\"https://opencollective.com/substitute-js\" alt=\"Financial Contributors on Open Collective\"\u003e\u003cimg src=\"https://opencollective.com/substitute-js/all/badge.svg?label=financial+contributors\" /\u003e\u003c/a\u003e \n\n# What is this?\n[`@fluffy-spoon/substitute`](https://www.npmjs.com/package/@fluffy-spoon/substitute) is a TypeScript port of [NSubstitute](http://nsubstitute.github.io), which aims to provide a much more fluent mocking opportunity for strong-typed languages.\n\n*You can read an in-depth comparison of `substitute.js` versus other popular TypeScript mocking frameworks here: https://medium.com/@mathiaslykkegaardlorenzen/with-typescript-3-and-substitute-js-you-are-already-missing-out-when-mocking-or-faking-a3b3240c4607*\n\n**PRs are very welcome! Help is much appreciated.**\n\n# Installing\n`npm install @fluffy-spoon/substitute --save-dev`\n\n# Requirements\n* `TypeScript^3.0.0`\n\n# Usage\n```typescript\nimport { Substitute, Arg } from '@fluffy-spoon/substitute';\n\ninterface Calculator {\n  add(a: number, b: number): number;\n  subtract(a: number, b: number): number;\n  divide(a: number, b: number): number;\n  async heavyOperation(): Promise\u003cnumber\u003e;\n\n  isEnabled: boolean;\n}\n\n// Create:\nconst calculator = Substitute.for\u003cCalculator\u003e();\n \n// Set a return value:\ncalculator.add(1, 2).returns(3);\n \n// Check received calls:\ncalculator.received().add(1, Arg.any());\ncalculator.didNotReceive().add(2, 2);\n```\n\n## Creating a mock\n`const calculator = Substitute.for\u003cCalculator\u003e();`\n\n## Setting return types\nSee the example below. The same syntax also applies to properties and fields.\n\n```typescript\n// single return type\ncalculator.add(1, 2).returns(4);\nconsole.log(calculator.add(1, 2)); // prints 4\nconsole.log(calculator.add(1, 2)); // prints undefined\n\n// multiple return types in sequence\ncalculator.add(1, 2).returns(3, 7, 9);\nconsole.log(calculator.add(1, 2)); // prints 3\nconsole.log(calculator.add(1, 2)); // prints 7\nconsole.log(calculator.add(1, 2)); // prints 9\nconsole.log(calculator.add(1, 2)); // prints undefined\n```\n\n## Working with promises\nWhen working with promises you can also use `resolves()` and `rejects()` to return a promise.\n\n```typescript\ncalculator.heavyOperation(1, 2).resolves(4); \n// same as calculator.heavyOperation(1, 2).returns(Promise.resolve(4));\nconsole.log(await calculator.heavyOperation(1, 2)); // prints 4\n```\n\n```typescript\ncalculator.heavyOperation(1, 2).rejects(new Error());\n// same as calculator.heavyOperation(1, 2).returns(Promise.reject(new Error()));\nconsole.log(await calculator.heavyOperation(1, 2)); // throws Error\n```\n\n## Verifying calls\n```typescript\ncalculator.enabled = true;\nconst foo = calculator.add(1, 2);\n\n// verify call to add(1, 2)\ncalculator.received().add(1, 2);\n\n// verify property set to \"true\"\ncalculator.received().enabled = true;\n```\n\n## Argument matchers\nThere are several ways of matching arguments. The examples below also applies to properties and fields - both when setting up calls and verifying them.\n\n### Matching specific arguments\n```typescript\nimport { Arg } from '@fluffy-spoon/substitute';\n\n// ignoring first argument\ncalculator.add(Arg.any(), 2).returns(10);\nconsole.log(calculator.add(1337, 3)); // prints undefined since second argument doesn't match\nconsole.log(calculator.add(1337, 2)); // prints 10 since second argument matches\n\n// received call with first arg 1 and second arg less than 0\ncalculator.received().add(1, Arg.is(x =\u003e x \u003c 0));\n```\n\n### Generic and inverse matchers\n```typescript\nimport { Arg } from '@fluffy-spoon/substitute';\n\nconst equalToZero = (x: number) =\u003e x === 0;\n\n// first argument will match any number\n// second argument will match a number that is not '0'\ncalculator.divide(Arg.any('number'), Arg.is.not(equalToZero)).returns(10);\nconsole.log(calculator.divide(100, 10)); // prints 10\n\nconst argIsNotZero = Arg.is.not(equalToZero);\ncalculator.received(1).divide(argIsNotZero, argIsNotZero);\n```\n\n\u003e #### Note: `Arg.is()` will automatically infer the type of the argument it's replacing \n\n### Ignoring all arguments\n```typescript\n// ignoring all arguments\ncalculator.add(Arg.all()).returns(10);\nconsole.log(calculator.add(1, 3)); // prints 10\nconsole.log(calculator.add(5, 2)); // prints 10\n```\n\n### Match order\nThe order of argument matchers matters. The first matcher that matches will always be used. Below are two examples.\n\n```typescript\ncalculator.add(Arg.all()).returns(10);\ncalculator.add(1, 3).returns(1337);\nconsole.log(calculator.add(1, 3)); // prints 10\nconsole.log(calculator.add(5, 2)); // prints 10\n```\n\n```typescript\ncalculator.add(1, 3).returns(1337);\ncalculator.add(Arg.all()).returns(10);\nconsole.log(calculator.add(1, 3)); // prints 1337\nconsole.log(calculator.add(5, 2)); // prints 10\n```\n\n## Partial mocks\nWith partial mocks you always start with a true substitute where everything is mocked and then opt-out of substitutions in certain scenarios.\n\n```typescript\nimport { Substitute, Arg } from '@fluffy-spoon/substitute';\n\nclass RealCalculator implements Calculator {\n  add(a: number, b: number) =\u003e a + b;\n  subtract(a: number, b: number) =\u003e a - b;\n  divide(a: number, b: number) =\u003e a / b;\n}\n\nconst realCalculator = new RealCalculator();\nconst fakeCalculator = Substitute.for\u003cCalculator\u003e();\n\n// let the subtract method always use the real method\nfakeCalculator.subtract(Arg.all()).mimicks(realCalculator.subtract);\nconsole.log(fakeCalculator.subtract(20, 10)); // prints 10\nconsole.log(fakeCalculator.subtract(1, 2)); // prints -1\n\n// for the add method, we only use the real method when the first arg is less than 10\n// else, we always return 1337\nfakeCalculator.add(Arg.is(x \u003c 10), Arg.any()).mimicks(realCalculator.add);\nfakeCalculator.add(Arg.is(x \u003e= 10), Arg.any()).returns(1337);\nconsole.log(fakeCalculator.add(5, 100)); // prints 105 via real method\nconsole.log(fakeCalculator.add(210, 7)); // prints 1337 via fake method\n\n// for the divide method, we only use the real method for explicit arguments\nfakeCalculator.divide(10, 2).mimicks(realCalculator.divide);\nfakeCalculator.divide(Arg.all()).returns(1338);\nconsole.log(fakeCalculator.divide(10, 5)); // prints 5\nconsole.log(fakeCalculator.divide(9, 5)); // prints 1338\n```\n\n## Throwing exceptions\nExceptions can be thrown on properties or methods. You can add different exceptions for different arguments\n\n```typescript\nimport { Substitute, Arg } from '@fluffy-spoon/substitute';\n\ninterface Calculator {\n  add(a: number, b: number): number;\n  subtract(a: number, b: number): number;\n  divide(a: number, b: number): number;\n  isEnabled: boolean;\n}\n\nconst calculator = Substitute.for\u003cCalculator\u003e();\ncalculator.divide(Arg.any(), 0).throws(new Error('Cannot divide by 0'));\ncalculator.divide(1, 0); // throws the exception Error: Cannot divide by 0\n```\n\n# Benefits over other mocking libraries\n- Easier-to-understand fluent syntax.\n- No need to cast to `any` in certain places (for instance, when overriding read-only properties) due to the `myProperty.returns(...)` syntax.\n- Doesn't weigh much.\n- Produces very clean and descriptive error messages. Try it out - you'll love it.\n- Doesn't rely on object instances - you can produce a strong-typed fake from nothing, ensuring that everything is mocked.\n\n# Beware\n## Names that conflict with Substitute.js\nLet's say we have a class with a method called `received`, `didNotReceive` or `mimick` keyword - how do we mock it? \n\nSimple! We disable the proxy methods temporarily while invoking the method by using the `disableFor` method which disables these special methods.\n\n```typescript\nclass Example {\n  received(someNumber: number) {\n    console.log(someNumber);\n  }\n}\n\nconst fake = Substitute.for\u003cExample\u003e();\n\n// BAD: this would have called substitute.js' \"received\" method.\n// fake.received(2);\n\n// GOOD: we now call the \"received\" method we have defined in the class above.\nSubstitute.disableFor(fake).received(1337);\n\n// now we can assert that we received a call to the \"received\" method.\nfake.received().received(1337);\n```\n\n## Contributors\n\n### Code Contributors\n\nThis project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)].\n\u003ca href=\"https://github.com/ffMathy/FluffySpoon.JavaScript.Testing.Faking/graphs/contributors\"\u003e\u003cimg src=\"https://opencollective.com/substitute-js/contributors.svg?width=890\u0026button=false\" /\u003e\u003c/a\u003e\n\n### Financial Contributors\n\nBecome a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/substitute-js/contribute)]\n\n#### Individuals\n\n\u003ca href=\"https://opencollective.com/substitute-js\"\u003e\u003cimg src=\"https://opencollective.com/substitute-js/individuals.svg?width=890\"\u003e\u003c/a\u003e\n\n#### Organizations\n\nSupport this project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/substitute-js/contribute)]\n\n\u003ca href=\"https://opencollective.com/substitute-js/organization/0/website\"\u003e\u003cimg src=\"https://opencollective.com/substitute-js/organization/0/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/substitute-js/organization/1/website\"\u003e\u003cimg src=\"https://opencollective.com/substitute-js/organization/1/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/substitute-js/organization/2/website\"\u003e\u003cimg src=\"https://opencollective.com/substitute-js/organization/2/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/substitute-js/organization/3/website\"\u003e\u003cimg src=\"https://opencollective.com/substitute-js/organization/3/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/substitute-js/organization/4/website\"\u003e\u003cimg src=\"https://opencollective.com/substitute-js/organization/4/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/substitute-js/organization/5/website\"\u003e\u003cimg src=\"https://opencollective.com/substitute-js/organization/5/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/substitute-js/organization/6/website\"\u003e\u003cimg src=\"https://opencollective.com/substitute-js/organization/6/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/substitute-js/organization/7/website\"\u003e\u003cimg src=\"https://opencollective.com/substitute-js/organization/7/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/substitute-js/organization/8/website\"\u003e\u003cimg src=\"https://opencollective.com/substitute-js/organization/8/avatar.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://opencollective.com/substitute-js/organization/9/website\"\u003e\u003cimg src=\"https://opencollective.com/substitute-js/organization/9/avatar.svg\"\u003e\u003c/a\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fffmathy%2Ffluffyspoon.javascript.testing.faking","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fffmathy%2Ffluffyspoon.javascript.testing.faking","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fffmathy%2Ffluffyspoon.javascript.testing.faking/lists"}