decorator-dependency-injection 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/release.yml +129 -0
- package/README.md +62 -12
- package/index.js +70 -48
- package/package.json +1 -1
- package/test/mock.test.js +20 -0
- package/test/proxy.test.js +73 -0
- package/.github/workflows/node.js.yml +0 -31
- package/.idea/decorator-depdenency-injection.iml +0 -13
- package/.idea/git_toolbox_blame.xml +0 -6
- package/.idea/git_toolbox_prj.xml +0 -15
- package/.idea/jsLibraryMappings.xml +0 -8
- package/.idea/material_theme_project_new.xml +0 -13
- package/.idea/modules.xml +0 -8
- package/.idea/vcs.xml +0 -6
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
name: Create Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [ "main" ]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [ "main" ]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
build:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
matrix:
|
|
14
|
+
node-version: [22.x, 23.x]
|
|
15
|
+
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
- name: Use Node.js ${{ matrix.node-version }}
|
|
19
|
+
uses: actions/setup-node@v4
|
|
20
|
+
with:
|
|
21
|
+
node-version: ${{ matrix.node-version }}
|
|
22
|
+
cache: 'npm'
|
|
23
|
+
- run: npm ci
|
|
24
|
+
- run: npm run build --if-present
|
|
25
|
+
- run: npm test
|
|
26
|
+
|
|
27
|
+
release:
|
|
28
|
+
needs: build
|
|
29
|
+
runs-on: ubuntu-latest
|
|
30
|
+
permissions:
|
|
31
|
+
contents: write
|
|
32
|
+
steps:
|
|
33
|
+
- name: Checkout Repository
|
|
34
|
+
uses: actions/checkout@v4
|
|
35
|
+
|
|
36
|
+
- name: Install Node.js
|
|
37
|
+
uses: actions/setup-node@v4
|
|
38
|
+
with:
|
|
39
|
+
node-version: '22'
|
|
40
|
+
|
|
41
|
+
- name: Get package.json version
|
|
42
|
+
id: package_version
|
|
43
|
+
run: echo "VERSION=$(node -p "require('./package.json').version")" >> $GITHUB_ENV
|
|
44
|
+
|
|
45
|
+
- name: Check if tag exists
|
|
46
|
+
id: check_tag
|
|
47
|
+
run: |
|
|
48
|
+
TAG_EXISTS=$(git ls-remote --tags origin "v${VERSION}" | wc -l)
|
|
49
|
+
echo "TAG_EXISTS=${TAG_EXISTS}" >> $GITHUB_ENV
|
|
50
|
+
|
|
51
|
+
- name: Get commit messages
|
|
52
|
+
id: commit_messages
|
|
53
|
+
run: |
|
|
54
|
+
COMMITS=$(git log --pretty=format:"%h - %s" $(git describe --tags --abbrev=0 @^)..@ | while read -r hash msg; do echo "[${hash}](https://github.com/${{ github.repository }}/commit/${hash}) - ${msg}"; done)
|
|
55
|
+
echo "COMMITS=${COMMITS}" >> $GITHUB_ENV
|
|
56
|
+
|
|
57
|
+
- name: Create Tag and Release
|
|
58
|
+
uses: actions/github-script@v7
|
|
59
|
+
with:
|
|
60
|
+
script: |
|
|
61
|
+
const version = process.env.VERSION
|
|
62
|
+
const tag = `v${version}`
|
|
63
|
+
const commits = process.env.COMMITS
|
|
64
|
+
const { data: tags } = await github.rest.repos.listTags({
|
|
65
|
+
owner: context.repo.owner,
|
|
66
|
+
repo: context.repo.repo,
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
if (!tags.find(t => t.name === tag)) {
|
|
70
|
+
await github.rest.git.createRef({
|
|
71
|
+
owner: context.repo.owner,
|
|
72
|
+
repo: context.repo.repo,
|
|
73
|
+
ref: `refs/tags/${tag}`,
|
|
74
|
+
sha: context.sha,
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
await github.rest.repos.createRelease({
|
|
78
|
+
owner: context.repo.owner,
|
|
79
|
+
repo: context.repo.repo,
|
|
80
|
+
tag_name: tag,
|
|
81
|
+
name: `Release ${tag}`,
|
|
82
|
+
body: `Automated release of version ${tag}\n\nCommits:\n${commits}`,
|
|
83
|
+
draft: false,
|
|
84
|
+
prerelease: false,
|
|
85
|
+
})
|
|
86
|
+
core.notice(`Release ${tag} created.`)
|
|
87
|
+
} else {
|
|
88
|
+
core.notice(`Release ${tag} already exists. No new release created.`)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
publish:
|
|
92
|
+
needs: release
|
|
93
|
+
runs-on: ubuntu-latest
|
|
94
|
+
steps:
|
|
95
|
+
- name: Checkout Repository
|
|
96
|
+
uses: actions/checkout@v4
|
|
97
|
+
|
|
98
|
+
- name: Install Node.js
|
|
99
|
+
uses: actions/setup-node@v4
|
|
100
|
+
with:
|
|
101
|
+
node-version: '22'
|
|
102
|
+
registry-url: 'https://registry.npmjs.org/'
|
|
103
|
+
|
|
104
|
+
- name: Get package.json version
|
|
105
|
+
id: package_version
|
|
106
|
+
run: echo "VERSION=$(node -p "require('./package.json').version")" >> $GITHUB_ENV
|
|
107
|
+
|
|
108
|
+
- name: Check if version exists on npm
|
|
109
|
+
id: check_npm_version
|
|
110
|
+
run: |
|
|
111
|
+
if npm show decorator-dependency-injection@${{ env.VERSION }} > /dev/null 2>&1; then
|
|
112
|
+
echo "VERSION_EXISTS=true" >> $GITHUB_ENV
|
|
113
|
+
else
|
|
114
|
+
echo "VERSION_EXISTS=false" >> $GITHUB_ENV
|
|
115
|
+
fi
|
|
116
|
+
|
|
117
|
+
- name: Publish to npm
|
|
118
|
+
if: env.VERSION_EXISTS == 'false'
|
|
119
|
+
run: |
|
|
120
|
+
npm publish --access public
|
|
121
|
+
echo "::notice::Published new version ${{ env.VERSION }} to npm."
|
|
122
|
+
env:
|
|
123
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
124
|
+
|
|
125
|
+
- name: Notice Publish Status
|
|
126
|
+
if: env.VERSION_EXISTS == 'true'
|
|
127
|
+
run: echo "::notice::Version ${{ env.VERSION }} already exists on npm. No new version published."
|
|
128
|
+
|
|
129
|
+
|
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
## Description
|
|
7
7
|
|
|
8
|
-
With TC39 reaching stage 3 on the decorators proposal, it's time to start thinking about how we can use them in our projects. One of the most common patterns in JavaScript is dependency injection. This pattern is used to make our code more testable and maintainable. This library provides simple decorators to help you inject dependencies into your classes and mock them for testing.
|
|
8
|
+
With [TC39](https://github.com/tc39/proposal-decorators) reaching stage 3 on the decorators proposal, it's time to start thinking about how we can use them in our projects. One of the most common patterns in JavaScript is dependency injection. This pattern is used to make our code more testable and maintainable. This library provides simple decorators to help you inject dependencies into your classes and mock them for testing.
|
|
9
9
|
|
|
10
10
|
## Installation
|
|
11
11
|
|
|
@@ -88,8 +88,8 @@ import { Factory, Inject } from 'decorator-dependency-injection';
|
|
|
88
88
|
@Factory
|
|
89
89
|
class Dependency {
|
|
90
90
|
constructor(param1, param2) {
|
|
91
|
-
this.param1 = param1
|
|
92
|
-
this.param2 = param2
|
|
91
|
+
this.param1 = param1
|
|
92
|
+
this.param2 = param2
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
95
|
|
|
@@ -105,33 +105,77 @@ While this is most useful for Factory dependencies, it can also be used with Sin
|
|
|
105
105
|
You can mock dependencies by using the ```@Mock``` decorator with a function that returns the mock dependency.
|
|
106
106
|
|
|
107
107
|
```javascript
|
|
108
|
-
import { Factory, Inject, Mock } from 'decorator-dependency-injection'
|
|
108
|
+
import { Factory, Inject, Mock } from 'decorator-dependency-injection'
|
|
109
109
|
|
|
110
110
|
@Factory
|
|
111
111
|
class Dependency {
|
|
112
112
|
method() {
|
|
113
|
-
return 'real'
|
|
113
|
+
return 'real'
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
class Consumer {
|
|
118
118
|
@Inject(Dependency) dependency
|
|
119
119
|
|
|
120
|
-
|
|
121
|
-
|
|
120
|
+
constructor() {
|
|
121
|
+
console.log(this.dependency.method())
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
-
|
|
125
|
+
// Test Code
|
|
126
|
+
|
|
127
|
+
@Mock(Dependency)
|
|
128
|
+
class MockDependency {
|
|
129
|
+
method() {
|
|
130
|
+
return 'mock'
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const consumer = new Consumer() // prints 'mock'
|
|
135
|
+
|
|
136
|
+
resetMock(Dependency)
|
|
137
|
+
|
|
138
|
+
const consumer = new Consumer() // prints 'real'
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
You can also use the ```@Mock``` decorator as a proxy instead of a full mock. Any method calls not implemented in the mock will be passed to the real dependency.
|
|
142
|
+
|
|
143
|
+
```javascript
|
|
144
|
+
import { Factory, Inject, Mock } from 'decorator-dependency-injection'
|
|
145
|
+
|
|
146
|
+
@Factory
|
|
147
|
+
class Dependency {
|
|
148
|
+
method() {
|
|
149
|
+
return 'real'
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
otherMethod() {
|
|
153
|
+
return 'other'
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
class Consumer {
|
|
158
|
+
@Inject(Dependency) dependency
|
|
159
|
+
|
|
160
|
+
constructor() {
|
|
161
|
+
console.log(this.dependency.method(), this.dependency.otherMethod())
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Test Code
|
|
166
|
+
|
|
167
|
+
@Mock(Dependency, true)
|
|
126
168
|
class MockDependency {
|
|
127
169
|
method() {
|
|
128
|
-
return 'mock'
|
|
170
|
+
return 'mock'
|
|
129
171
|
}
|
|
130
172
|
}
|
|
131
173
|
|
|
132
|
-
const consumer = new Consumer()
|
|
174
|
+
const consumer = new Consumer() // prints 'mock other'
|
|
133
175
|
|
|
134
|
-
|
|
176
|
+
resetMock(Dependency)
|
|
177
|
+
|
|
178
|
+
const consumer = new Consumer() // prints 'real other'
|
|
135
179
|
```
|
|
136
180
|
|
|
137
181
|
For more examples, see the tests in the ```test``` directory.
|
|
@@ -142,4 +186,10 @@ To run the tests, run the following command in the project root.
|
|
|
142
186
|
|
|
143
187
|
```bash
|
|
144
188
|
npm test
|
|
145
|
-
```
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Version History
|
|
192
|
+
|
|
193
|
+
- 1.0.0 - Initial release
|
|
194
|
+
- 1.0.1 - Automated release with GitHub Actions
|
|
195
|
+
- 1.0.2 - Added proxy option to @Mock decorator
|
package/index.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @typedef {Object} InstanceContext
|
|
3
|
+
* @property {string} type The type of the instance, either 'singleton' or 'factory'
|
|
3
4
|
* @property {Class} clazz The class of the instance
|
|
5
|
+
* @property {Class} [originalClazz] The original class if it is a mock
|
|
4
6
|
* @property {Object} [instance] The instance if it is a singleton
|
|
5
|
-
* @property {
|
|
7
|
+
* @property {Object} [originalInstance] The original instance if it is a mock
|
|
8
|
+
* @property {boolean} [proxy=false] If true, the mock if the injection instance will be a proxy to the original class
|
|
6
9
|
*/
|
|
7
10
|
|
|
8
11
|
/** @type {Map<string|Class, InstanceContext>} */
|
|
9
|
-
const
|
|
10
|
-
/** @type {Map<string|Class, InstanceContext>} */
|
|
11
|
-
const factories = new Map()
|
|
12
|
+
const instances = new Map()
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Register a class as a singleton. If a name is provided, it will be used as the key in the singleton map.
|
|
@@ -27,13 +28,10 @@ export function Singleton(name) {
|
|
|
27
28
|
if (context.kind !== "class") {
|
|
28
29
|
throw new Error('Invalid injection target')
|
|
29
30
|
}
|
|
30
|
-
if (
|
|
31
|
-
throw new Error('
|
|
32
|
-
}
|
|
33
|
-
if (factories.has(name ?? clazz)) {
|
|
34
|
-
throw new Error('Factory with the same name already defined')
|
|
31
|
+
if (instances.has(name ?? clazz)) {
|
|
32
|
+
throw new Error('Instance with that name or class already instantiated')
|
|
35
33
|
}
|
|
36
|
-
|
|
34
|
+
instances.set(name ?? clazz, { clazz, type: 'singleton' })
|
|
37
35
|
}
|
|
38
36
|
}
|
|
39
37
|
|
|
@@ -54,13 +52,10 @@ export function Factory(name) {
|
|
|
54
52
|
if (context.kind !== "class") {
|
|
55
53
|
throw new Error('Invalid injection target')
|
|
56
54
|
}
|
|
57
|
-
if (
|
|
58
|
-
throw new Error('
|
|
59
|
-
}
|
|
60
|
-
if (singletons.has(name ?? clazz)) {
|
|
61
|
-
throw new Error('Singleton with the same name already defined')
|
|
55
|
+
if (instances.has(name ?? clazz)) {
|
|
56
|
+
throw new Error('Instance with that name or class already instantiated')
|
|
62
57
|
}
|
|
63
|
-
|
|
58
|
+
instances.set(name ?? clazz, { clazz, type: 'factory' })
|
|
64
59
|
}
|
|
65
60
|
}
|
|
66
61
|
|
|
@@ -69,7 +64,7 @@ export function Factory(name) {
|
|
|
69
64
|
* If the instance is a singleton, it will only be created once with the first set of parameters it encounters.
|
|
70
65
|
*
|
|
71
66
|
* @param {string|Class} clazzOrName The singleton or factory class or name
|
|
72
|
-
* @param {*} params Parameters to pass to the constructor
|
|
67
|
+
* @param {*} params Parameters to pass to the constructor. Recommended to use only with factories.
|
|
73
68
|
* @return {(function(*): void)|*}
|
|
74
69
|
* @example @Inject(MySingleton) mySingleton
|
|
75
70
|
* @example @Inject("myCustomName") myFactory
|
|
@@ -83,18 +78,32 @@ export function Inject(clazzOrName, ...params) {
|
|
|
83
78
|
if (initialValue) {
|
|
84
79
|
throw new Error('Cannot assign value to injected field')
|
|
85
80
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
81
|
+
const instanceContext = getContext(clazzOrName)
|
|
82
|
+
|
|
83
|
+
if (instanceContext.instance) {
|
|
84
|
+
return instanceContext.instance
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const instance = new instanceContext.clazz(...params)
|
|
88
|
+
|
|
89
|
+
if (instanceContext.type === 'singleton') {
|
|
90
|
+
if (instanceContext.originalClazz && instanceContext.proxy) {
|
|
91
|
+
instanceContext.instance = getProxy(instance, new instanceContext.originalClazz(...params))
|
|
92
|
+
} else {
|
|
93
|
+
instanceContext.instance = instance
|
|
90
94
|
}
|
|
91
|
-
return
|
|
92
|
-
} else if (factories.has(clazzOrName)) {
|
|
93
|
-
const factoryClass = factories.get(clazzOrName).clazz
|
|
94
|
-
return new factoryClass(...params)
|
|
95
|
-
} else {
|
|
96
|
-
throw new Error('Cannot find injection source with the provided name')
|
|
95
|
+
return instanceContext.instance
|
|
97
96
|
}
|
|
97
|
+
|
|
98
|
+
if (instanceContext.type === 'factory') {
|
|
99
|
+
if (instanceContext.originalClazz && instanceContext.proxy) {
|
|
100
|
+
return getProxy(instance, new instanceContext.originalClazz(...params))
|
|
101
|
+
} else {
|
|
102
|
+
return instance
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
throw new Error('Unexpected injection type')
|
|
98
107
|
}
|
|
99
108
|
} else {
|
|
100
109
|
throw new Error('Invalid injection target')
|
|
@@ -102,31 +111,47 @@ export function Inject(clazzOrName, ...params) {
|
|
|
102
111
|
}
|
|
103
112
|
}
|
|
104
113
|
|
|
114
|
+
function getProxy(mock, original) {
|
|
115
|
+
return new Proxy(mock, {
|
|
116
|
+
get(target, prop, receiver) {
|
|
117
|
+
if (prop in target) {
|
|
118
|
+
return Reflect.get(target, prop, receiver)
|
|
119
|
+
}
|
|
120
|
+
return Reflect.get(original, prop, receiver)
|
|
121
|
+
}
|
|
122
|
+
})
|
|
123
|
+
}
|
|
124
|
+
|
|
105
125
|
/**
|
|
106
126
|
* Mark a class as a mock. This will replace the class with a mock instance when injected.
|
|
107
127
|
* @param {string|Class} mockedClazzOrName The singleton or factory class or name to be mocked
|
|
128
|
+
* @param {boolean} [proxy=false] If true, the mock will be a proxy to the original class. Any methods not defined in the mock will be called on the original class.
|
|
108
129
|
* @return {(function(*, *): void)|*}
|
|
109
130
|
* @example @Mock(MySingleton) class MyMock {}
|
|
110
131
|
* @example @Mock("myCustomName") class MyMock {}
|
|
111
132
|
* @throws {Error} If the injection target is not a class
|
|
112
133
|
* @throws {Error} If the injection source is not found
|
|
113
134
|
*/
|
|
114
|
-
export function Mock(mockedClazzOrName) {
|
|
135
|
+
export function Mock(mockedClazzOrName, proxy = false) {
|
|
115
136
|
return function(clazz, context) {
|
|
116
137
|
if (context.kind !== "class") {
|
|
117
138
|
throw new Error('Invalid injection target')
|
|
118
139
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
instanceContext.clazz = clazz
|
|
123
|
-
} else if (factories.has(mockedClazzOrName)) {
|
|
124
|
-
const instanceContext = factories.get(mockedClazzOrName)
|
|
125
|
-
instanceContext.original = instanceContext.clazz
|
|
126
|
-
instanceContext.clazz = clazz
|
|
127
|
-
} else {
|
|
128
|
-
throw new Error('Cannot find injection source with the provided name')
|
|
140
|
+
const instanceContext = getContext(mockedClazzOrName)
|
|
141
|
+
if (instanceContext.originalClazz) {
|
|
142
|
+
throw new Error('Mock already defined, reset before mocking again')
|
|
129
143
|
}
|
|
144
|
+
instanceContext.originalClazz = instanceContext.clazz
|
|
145
|
+
instanceContext.proxy = proxy
|
|
146
|
+
instanceContext.clazz = clazz
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function getContext(mockedClazzOrName) {
|
|
151
|
+
if (instances.has(mockedClazzOrName)) {
|
|
152
|
+
return instances.get(mockedClazzOrName)
|
|
153
|
+
} else {
|
|
154
|
+
throw new Error('Cannot find injection source with the provided name')
|
|
130
155
|
}
|
|
131
156
|
}
|
|
132
157
|
|
|
@@ -134,10 +159,7 @@ export function Mock(mockedClazzOrName) {
|
|
|
134
159
|
* Reset all mocks to their original classes.
|
|
135
160
|
*/
|
|
136
161
|
export function resetMocks() {
|
|
137
|
-
for (const instanceContext of
|
|
138
|
-
reset(instanceContext)
|
|
139
|
-
}
|
|
140
|
-
for (const instanceContext of factories.values()) {
|
|
162
|
+
for (const instanceContext of instances.values()) {
|
|
141
163
|
reset(instanceContext)
|
|
142
164
|
}
|
|
143
165
|
}
|
|
@@ -147,8 +169,7 @@ export function resetMocks() {
|
|
|
147
169
|
* @param {string|Class} clazzOrName The singleton or factory class or name to reset
|
|
148
170
|
*/
|
|
149
171
|
export function resetMock(clazzOrName) {
|
|
150
|
-
|
|
151
|
-
reset(instanceContext)
|
|
172
|
+
reset(getContext(clazzOrName))
|
|
152
173
|
}
|
|
153
174
|
|
|
154
175
|
/**
|
|
@@ -160,9 +181,10 @@ function reset(instanceContext) {
|
|
|
160
181
|
if (!instanceContext) {
|
|
161
182
|
throw new Error('Cannot find injection source with the provided name')
|
|
162
183
|
}
|
|
163
|
-
if (instanceContext.
|
|
164
|
-
instanceContext.clazz = instanceContext.
|
|
165
|
-
|
|
166
|
-
delete instanceContext.
|
|
184
|
+
if (instanceContext.originalClazz) {
|
|
185
|
+
instanceContext.clazz = instanceContext.originalClazz
|
|
186
|
+
instanceContext.instance = instanceContext.originalInstance
|
|
187
|
+
delete instanceContext.originalClazz
|
|
188
|
+
delete instanceContext.originalInstance
|
|
167
189
|
}
|
|
168
190
|
}
|
package/package.json
CHANGED
package/test/mock.test.js
CHANGED
|
@@ -6,12 +6,20 @@ describe('Mocking', () => {
|
|
|
6
6
|
op() {
|
|
7
7
|
return 'original'
|
|
8
8
|
}
|
|
9
|
+
|
|
10
|
+
op2() {
|
|
11
|
+
return 'original2'
|
|
12
|
+
}
|
|
9
13
|
}
|
|
10
14
|
|
|
11
15
|
class TestInjection {
|
|
12
16
|
@Inject(ToBeMockedSingleton) toBeMockedSingleton
|
|
13
17
|
}
|
|
14
18
|
|
|
19
|
+
afterEach(() => {
|
|
20
|
+
resetMocks()
|
|
21
|
+
})
|
|
22
|
+
|
|
15
23
|
it('should inject a mock singleton', () => {
|
|
16
24
|
@Mock(ToBeMockedSingleton)
|
|
17
25
|
class MockedSingleton {
|
|
@@ -22,10 +30,12 @@ describe('Mocking', () => {
|
|
|
22
30
|
|
|
23
31
|
const result = new TestInjection()
|
|
24
32
|
expect(result.toBeMockedSingleton.op()).toBe('mocked')
|
|
33
|
+
expect(result.toBeMockedSingleton.op2).toBe.undefined
|
|
25
34
|
|
|
26
35
|
resetMocks()
|
|
27
36
|
const result2 = new TestInjection()
|
|
28
37
|
expect(result2.toBeMockedSingleton.op()).toBe('original')
|
|
38
|
+
expect(result2.toBeMockedSingleton.op2()).toBe('original2')
|
|
29
39
|
})
|
|
30
40
|
|
|
31
41
|
@Factory()
|
|
@@ -54,4 +64,14 @@ describe('Mocking', () => {
|
|
|
54
64
|
const result2 = new TestInjectionFactory()
|
|
55
65
|
expect(result2.toBeMockedFactory.op()).toBe('original')
|
|
56
66
|
})
|
|
67
|
+
|
|
68
|
+
it('should throw an error if a mock is not a singleton or factory', () => {
|
|
69
|
+
expect(() => {
|
|
70
|
+
@Mock(ToBeMockedFactory)
|
|
71
|
+
class Mocked1 {}
|
|
72
|
+
|
|
73
|
+
@Mock(ToBeMockedFactory)
|
|
74
|
+
class Mocked2 {}
|
|
75
|
+
}).toThrow('Mock already defined, reset before mocking again')
|
|
76
|
+
})
|
|
57
77
|
})
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import {resetMocks, Inject, Mock, Singleton, Factory} from '../index.js'
|
|
2
|
+
|
|
3
|
+
describe('Proxy Mocking', () => {
|
|
4
|
+
@Singleton()
|
|
5
|
+
class ToBeProxiedSingleton {
|
|
6
|
+
op() {
|
|
7
|
+
return 'original'
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
op2() {
|
|
11
|
+
return 'original2'
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
class TestInjection {
|
|
16
|
+
@Inject(ToBeProxiedSingleton) toBeProxiedSingleton
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
afterEach(() => {
|
|
20
|
+
resetMocks()
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('should inject a proxy singleton', () => {
|
|
24
|
+
@Mock(ToBeProxiedSingleton, true)
|
|
25
|
+
class ProxiedSingleton {
|
|
26
|
+
op() {
|
|
27
|
+
return 'mocked'
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const result = new TestInjection()
|
|
32
|
+
expect(result.toBeProxiedSingleton.op()).toBe('mocked')
|
|
33
|
+
expect(result.toBeProxiedSingleton.op2()).toBe('original2')
|
|
34
|
+
|
|
35
|
+
resetMocks()
|
|
36
|
+
const result2 = new TestInjection()
|
|
37
|
+
expect(result2.toBeProxiedSingleton.op()).toBe('original')
|
|
38
|
+
expect(result2.toBeProxiedSingleton.op2()).toBe('original2')
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
@Factory()
|
|
42
|
+
class ToBeProxiedFactory {
|
|
43
|
+
op() {
|
|
44
|
+
return 'original'
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
op2() {
|
|
48
|
+
return 'original2'
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
class TestInjectionFactory {
|
|
53
|
+
@Inject(ToBeProxiedFactory) toBeProxiedFactory
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
it('should inject a proxy factory', () => {
|
|
57
|
+
@Mock(ToBeProxiedFactory, true)
|
|
58
|
+
class ProxiedFactory {
|
|
59
|
+
op() {
|
|
60
|
+
return 'mocked'
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const result = new TestInjectionFactory()
|
|
65
|
+
expect(result.toBeProxiedFactory.op()).toBe('mocked')
|
|
66
|
+
expect(result.toBeProxiedFactory.op2()).toBe('original2')
|
|
67
|
+
|
|
68
|
+
resetMocks()
|
|
69
|
+
const result2 = new TestInjectionFactory()
|
|
70
|
+
expect(result2.toBeProxiedFactory.op()).toBe('original')
|
|
71
|
+
expect(result2.toBeProxiedFactory.op2()).toBe('original2')
|
|
72
|
+
})
|
|
73
|
+
})
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
|
|
2
|
-
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs
|
|
3
|
-
|
|
4
|
-
name: Node.js CI
|
|
5
|
-
|
|
6
|
-
on:
|
|
7
|
-
push:
|
|
8
|
-
branches: [ "main" ]
|
|
9
|
-
pull_request:
|
|
10
|
-
branches: [ "main" ]
|
|
11
|
-
|
|
12
|
-
jobs:
|
|
13
|
-
build:
|
|
14
|
-
|
|
15
|
-
runs-on: ubuntu-latest
|
|
16
|
-
|
|
17
|
-
strategy:
|
|
18
|
-
matrix:
|
|
19
|
-
node-version: [18.x, 20.x, 22.x]
|
|
20
|
-
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
|
21
|
-
|
|
22
|
-
steps:
|
|
23
|
-
- uses: actions/checkout@v4
|
|
24
|
-
- name: Use Node.js ${{ matrix.node-version }}
|
|
25
|
-
uses: actions/setup-node@v4
|
|
26
|
-
with:
|
|
27
|
-
node-version: ${{ matrix.node-version }}
|
|
28
|
-
cache: 'npm'
|
|
29
|
-
- run: npm ci
|
|
30
|
-
- run: npm run build --if-present
|
|
31
|
-
- run: npm test
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
-
<module type="WEB_MODULE" version="4">
|
|
3
|
-
<component name="NewModuleRootManager">
|
|
4
|
-
<content url="file://$MODULE_DIR$">
|
|
5
|
-
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
|
6
|
-
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
|
7
|
-
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
|
8
|
-
</content>
|
|
9
|
-
<orderEntry type="inheritedJdk" />
|
|
10
|
-
<orderEntry type="sourceFolder" forTests="false" />
|
|
11
|
-
<orderEntry type="library" name="@types/jest" level="application" />
|
|
12
|
-
</component>
|
|
13
|
-
</module>
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
-
<project version="4">
|
|
3
|
-
<component name="GitToolBoxProjectSettings">
|
|
4
|
-
<option name="commitMessageIssueKeyValidationOverride">
|
|
5
|
-
<BoolValueOverride>
|
|
6
|
-
<option name="enabled" value="true" />
|
|
7
|
-
</BoolValueOverride>
|
|
8
|
-
</option>
|
|
9
|
-
<option name="commitMessageValidationEnabledOverride">
|
|
10
|
-
<BoolValueOverride>
|
|
11
|
-
<option name="enabled" value="true" />
|
|
12
|
-
</BoolValueOverride>
|
|
13
|
-
</option>
|
|
14
|
-
</component>
|
|
15
|
-
</project>
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
-
<project version="4">
|
|
3
|
-
<component name="JavaScriptLibraryMappings">
|
|
4
|
-
<file url="file://$PROJECT_DIR$" libraries="{Node.js Core, decorator-depdenency-injection/node_modules}" />
|
|
5
|
-
<file url="file://$PROJECT_DIR$/test" libraries="{@types/jest, Node.js Core, decorator-depdenency-injection/node_modules}" />
|
|
6
|
-
<excludedPredefinedLibrary name="HTML" />
|
|
7
|
-
</component>
|
|
8
|
-
</project>
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
-
<project version="4">
|
|
3
|
-
<component name="MaterialThemeProjectNewConfig">
|
|
4
|
-
<option name="metadata">
|
|
5
|
-
<MTProjectMetadataState>
|
|
6
|
-
<option name="migrated" value="true" />
|
|
7
|
-
<option name="pristineConfig" value="false" />
|
|
8
|
-
<option name="userId" value="-1911ddbb:1739357c210:-8000" />
|
|
9
|
-
<option name="version" value="8.13.2" />
|
|
10
|
-
</MTProjectMetadataState>
|
|
11
|
-
</option>
|
|
12
|
-
</component>
|
|
13
|
-
</project>
|
package/.idea/modules.xml
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
-
<project version="4">
|
|
3
|
-
<component name="ProjectModuleManager">
|
|
4
|
-
<modules>
|
|
5
|
-
<module fileurl="file://$PROJECT_DIR$/.idea/decorator-depdenency-injection.iml" filepath="$PROJECT_DIR$/.idea/decorator-depdenency-injection.iml" />
|
|
6
|
-
</modules>
|
|
7
|
-
</component>
|
|
8
|
-
</project>
|