Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Showcase #replace.usingAccessor for DI in the typescript case study #2556

Merged
merged 4 commits into from
Oct 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ coverage/
_site
docs/vendor/
vendor/
.bundle
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ tmp/
docs/_site/
docs/js/
docs/assets/js/
vendor
51 changes: 48 additions & 3 deletions docs/_howto/typescript-swc.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,9 +190,11 @@ sandbox.replaceGetter(Other, "toBeMocked", () => stub);

## Use pure dependency injection

### Version 1 : full manual mode

> [Working code][pure-di]

This technique works regardless of language, module systems, bundlers and tool chains, but requires slight modifications of the SUT to allow modifying it. Sinon cannot help with resetting state automatically in this scenario.
This technique works regardless of language, module systems, bundlers and tool chains, but requires slight modifications of the SUT to allow modifying it. Sinon does not help in resetting state automatically in this scenario.

**other.ts**

Expand Down Expand Up @@ -225,11 +227,54 @@ describe("main", () => {
});
```

### Version 2: using Sinon's auto-cleanup

> [Working code][pure-di-with-auto-cleanup]

This is a slight variation of the fully manual dependency injection version, but with a twist to
make it nicer. Sinon 16.1 gained the ability to assign and restore values that were defined
using _accessors_. That means, that if you expose an object with setters and getters for props
you would like to replace, you can get Sinon to clean up after you. The example above becomes much nicer:

**other.ts**

```typescript
function _toBeMocked() {
return "I am the original function";
}

export let toBeMocked = _toBeMocked;

export const myMocks = {
set toBeMocked(mockImplementation) {
toBeMocked = mockImplementation;
},
get toBeMocked() {
return _toBeMocked;
},
};
```

**main.spec.ts**

```typescript
describe("main", () => {

after(() => sandbox.restore())

it("should mock", () => {
mocked = sandbox.fake.returns("mocked");
sandbox.replace.usingAccessor(Other.myMocks, 'toBeMocked', mocked)
main();
expect(mocked.called).to.be.true;
});
```

## Hooking into Node's module loading

> [Working code][cjs-mocking]

This is what [the article on _targetting the link seams_][link-seams-cjs] is about. The only difference here is using Quibble instead of Proxyquire. Quibble is slightly terser and also supports being used as a ESM _loader_, making it a bit more modern and useful. The end result:
This is what [the article on _targetting the link seams_][link-seams-cjs] is about. The only difference here is using Quibble instead of Proxyquire. Quibble is slightly terser and also supports being used as a ESM _loader_, making it a bit more modern and useful. The end result looks like this:

```typescript
describe("main module", () => {
Expand Down Expand Up @@ -261,5 +306,5 @@ As can be seen, there are lots of different paths to walk in order to achieve th
[sut]: http://xunitpatterns.com/SUT.html
[require-hook]: https://levelup.gitconnected.com/how-to-add-hooks-to-node-js-require-function-dee7acd12698
[swc-mutable-export]: https://github.com/fatso83/sinon-swc-bug/tree/swc-with-mutable-exports
[pure-di]: https://github.com/fatso83/sinon-swc-bug/tree/pure-di
[pure-di-with-auto-cleanup]: https://github.com/fatso83/sinon-swc-bug/tree/auto-cleanup-di
[cjs-mocking]: https://github.com/fatso83/sinon-swc-bug/tree/cjs-mocking
4 changes: 4 additions & 0 deletions docs/assets/css/main.scss
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,10 @@ body {
// Content
.content {
padding: 60px 0px;

& h5 {
font-weight: 600; // make h5 visible: otherwise no difference is shown
}
}

// Pages
Expand Down
2 changes: 1 addition & 1 deletion docs/release-source/release/sandbox.md
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ Usually one intends to _replace_ the value or getter of a field, but there are u

##### Use case: no-frills dependency injection in ESM with cleanup

One use case can be to conveniently allow ESM module stubbing using pure dependency injection, having Sinon help you with the cleanup, without resorting to external machinery such as module loaders or require hooks (see [#2403](https://github.com/sinonjs/sinon/issues/2403)). This would then work regardless of bundler, browser or server environment.
One use case can be to conveniently allow ESM module stubbing using pure dependency injection, having Sinon help you with the cleanup, without resorting to external machinery such as module loaders or require hooks (see [the case study on module mocking Typescript](/how-to/typescript-swc/#version-2-using-sinons-auto-cleanup) for an example). This approach works regardless of bundler, browser or server environment.

#### `sandbox.replaceGetter(object, property, replacementFunction);`

Expand Down
19 changes: 0 additions & 19 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 4 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
"test": "npm run test-node && npm run test-headless && npm run test-webworker",
"check-dependencies": "dependency-check package.json --no-dev --ignore-module esm",
"build": "node ./build.cjs",
"dev-docs": "cd docs; cp -rl release-source releases/dev; npm run serve-docs",
"dev-docs": "cd docs; rsync -r --delete release-source/ releases/dev; npm run serve-docs",
"build-docs": "cd docs; bundle exec jekyll build",
"serve-docs": "cd docs; bundle exec jekyll serve --incremental --verbose --livereload",
"lint": "eslint --max-warnings 101 '**/*.{js,cjs,mjs}'",
Expand All @@ -58,7 +58,8 @@
"prettier:write": "prettier --write '**/*.{js,css,md}'",
"preversion": "./scripts/preversion.sh",
"version": "./scripts/version.sh",
"postversion": "./scripts/postversion.sh"
"postversion": "./scripts/postversion.sh",
"postinstall": "git config --replace-all core.hooksPath scripts/hooks"
},
"nyc": {
"instrument": false,
Expand All @@ -69,7 +70,7 @@
]
},
"lint-staged": {
"*.{js,css,md}": "prettier --check",
"**/*.{js,css,md}": "prettier --write",
"*.js": "eslint --quiet",
"*.mjs": "eslint --quiet --ext mjs --parser-options=sourceType:module"
},
Expand All @@ -92,7 +93,6 @@
"browserify": "^16.5.2",
"debug": "^4.3.4",
"dependency-check": "^4.1.0",
"husky": "^6.0.0",
"lint-staged": "^13.2.0",
"mocha": "^10.2.0",
"mochify": "^9.2.0",
Expand Down Expand Up @@ -137,10 +137,5 @@
"cache": true
},
"mode": "auto"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
}
}
1 change: 1 addition & 0 deletions scripts/hooks/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
npx lint-staged $@
Loading