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

Improve assertion messages when running expectations #281

Merged
merged 10 commits into from
Mar 21, 2024
9 changes: 5 additions & 4 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,18 @@ jobs:
strategy:
fail-fast: false
matrix:
language: ['javascript']
language: ['javascript-typescript']
# Learn more...
# https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection

steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
fetch-depth: 2
show-progress: false

# If this run was triggered by a pull request event, then checkout
# the head of the pull request instead of the merge commit.
Expand All @@ -35,9 +36,9 @@ jobs:

# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
uses: github/codeql-action/analyze@v3
8 changes: 5 additions & 3 deletions .github/workflows/nodejs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [12, 14, 16, 18, 20]
node-version: [18, 20, 21]

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
show-progress: false
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
Expand Down
24 changes: 15 additions & 9 deletions .github/workflows/npm-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ jobs:
build-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- uses: actions/checkout@v4
with:
node-version: 14
show-progress: false
- uses: actions/setup-node@v4
with:
node-version: 18
cache: 'npm'
- run: npm ci --ignore-scripts
- run: npm test
Expand All @@ -20,10 +22,12 @@ jobs:
needs: build-test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- uses: actions/checkout@v4
with:
show-progress: false
- uses: actions/setup-node@v4
with:
node-version: 14
node-version: 18
registry-url: https://registry.npmjs.org/
cache: 'npm'
- run: npm ci --ignore-scripts
Expand All @@ -43,10 +47,12 @@ jobs:
contents: read
packages: write
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- uses: actions/checkout@v4
with:
show-progress: false
- uses: actions/setup-node@v4
with:
node-version: 14
node-version: 18
registry-url: https://npm.pkg.github.com/
cache: 'npm'
- run: npm ci --ignore-scripts
Expand Down
40 changes: 29 additions & 11 deletions package-lock.json

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

7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@fluffy-spoon/substitute",
"version": "2.0.0-beta.3",
"version": "2.0.0-beta.4",
"description": "TypeScript port of NSubstitute, which aims to provide a much more fluent mocking opportunity for strong-typed languages",
"license": "MIT",
"funding": {
Expand Down Expand Up @@ -46,9 +46,12 @@
"devDependencies": {
"@ava/typescript": "^3.0.1",
"@sinonjs/fake-timers": "^11.2.2",
"@types/node": "^12.20.55",
"@types/node": "^18.19.22",
"@types/sinonjs__fake-timers": "^8.1.5",
"ava": "^4.3.3",
"typescript": "^4.8.4"
},
"volta": {
"node": "18.19.1"
}
}
28 changes: 18 additions & 10 deletions spec/regression/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,13 +121,19 @@ test('class method received', t => {
t.notThrows(() => substitute.received(1).c('hi', 'the1re'))
t.notThrows(() => substitute.received().c('hi', 'there'))

const expectedMessage = 'Expected 7 calls to the method c with arguments [\'hi\', \'there\'], but received 4 of such calls.\n' +
'All calls received to method c:\n' +
'-> call with arguments [\'hi\', \'there\']\n' +
'-> call with arguments [\'hi\', \'the1re\']\n' +
'-> call with arguments [\'hi\', \'there\']\n' +
'-> call with arguments [\'hi\', \'there\']\n' +
'-> call with arguments [\'hi\', \'there\']'
const expectedMessage = 'Call count mismatch in @Substitute.c:\n' +
`Expected to receive 7 method calls matching c('hi', 'there'), but received 4.\n` +
'All property or method calls to @Substitute.c received so far:\n' +
`› ✔ @Substitute.c('hi', 'there')\n` +
` called at <anonymous> (${process.cwd()}/spec/regression/index.test.ts:114:18)\n` +
`› ✘ @Substitute.c('hi', 'the1re')\n` +
` called at <anonymous> (${process.cwd()}/spec/regression/index.test.ts:115:18)\n` +
`› ✔ @Substitute.c('hi', 'there')\n` +
` called at <anonymous> (${process.cwd()}/spec/regression/index.test.ts:116:18)\n` +
`› ✔ @Substitute.c('hi', 'there')\n` +
` called at <anonymous> (${process.cwd()}/spec/regression/index.test.ts:117:18)\n` +
`› ✔ @Substitute.c('hi', 'there')\n` +
` called at <anonymous> (${process.cwd()}/spec/regression/index.test.ts:118:18)\n`
const { message } = t.throws(() => { substitute.received(7).c('hi', 'there') })
t.is(message.replace(textModifierRegex, ''), expectedMessage)
})
Expand All @@ -142,9 +148,11 @@ test('received call matches after partial mocks using property instance mimicks'
substitute.received(1).c('lala', 'bar')

t.notThrows(() => substitute.received(1).c('lala', 'bar'))
const expectedMessage = 'Expected 2 calls to the method c with arguments [\'lala\', \'bar\'], but received 1 of such calls.\n' +
'All calls received to method c:\n' +
'-> call with arguments [\'lala\', \'bar\']'
const expectedMessage = 'Call count mismatch in @Substitute.c:\n' +
`Expected to receive 2 method calls matching c('lala', 'bar'), but received 1.\n` +
'All property or method calls to @Substitute.c received so far:\n' +
`› ✔ @Substitute.c('lala', 'bar')\n` +
` called at <anonymous> (${process.cwd()}/spec/regression/index.test.ts:145:13)\n`
const { message } = t.throws(() => substitute.received(2).c('lala', 'bar'))
t.is(message.replace(textModifierRegex, ''), expectedMessage)
t.deepEqual(substitute.d, 1337)
Expand Down
13 changes: 13 additions & 0 deletions spec/regression/issues/138.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import test from 'ava'

import { Substitute } from '../../../src'

interface Library { }

test('issue 138: serializes to JSON compatible data', t => {
const lib = Substitute.for<Library>()
const result = JSON.stringify(lib)

t.true(typeof result === 'string')
t.is(result, '"@Substitute {\\n}"')
})
39 changes: 39 additions & 0 deletions spec/regression/issues/27.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import test from 'ava'
import { types } from 'util'

import { Substitute } from '../../../src'

interface Library {
subSection: () => string
}

// Adapted snipped extracted from https://github.com/angular/angular/blob/main/packages/compiler/src/parse_util.ts#L176
// This is to reproduce the behavior of the Angular compiler. This function tries to extract the id from a reference.
const identifierName = (compileIdentifier: { reference: any } | null | undefined): string | null => {
if (!compileIdentifier || !compileIdentifier.reference) {
return null
}
const ref = compileIdentifier.reference
if (ref['__anonymousType']) {
return ref['__anonymousType']
}
}

test('issue 27: mocks should work with Angular TestBed', t => {
const lib = Substitute.for<Library>()
lib.subSection().returns('This is the mocked value')
const result = identifierName({ reference: lib })

t.not(result, null)
t.true(types.isProxy(result))

const jitId = `jit_${result}`
t.is(jitId, 'jit_property<__anonymousType>: ')
})

test('issue 27: subsitute node can be coerced to a primitive value', t => {
const lib = Substitute.for<Library>()
t.true(typeof `${lib}` === 'string')
t.true(typeof (lib + '') === 'string')
t.is(`${lib}`, '@Substitute {\n}')
})
13 changes: 8 additions & 5 deletions src/Arguments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@ type ArgumentOptions = {
inverseMatch?: boolean
}
class BaseArgument<T> {
private _description: string
constructor(
private _description: string,
description: string,
private _matchingFunction: PredicateFunction<T>,
private _options?: ArgumentOptions
) { }
) {
this._description = `${this._options?.inverseMatch ? 'Not ' : ''}${description}`
}

matches(arg: T) {
const inverseMatch = this._options?.inverseMatch ?? false
Expand All @@ -33,7 +36,7 @@ export class Argument<T> extends BaseArgument<T> {
export class AllArguments<T extends any[]> extends BaseArgument<T> {
private readonly _type = 'AllArguments';
constructor() {
super('{all}', () => true, {})
super('Arg.all{}', () => true, {})
}
get type(): 'AllArguments' {
return this._type // TODO: Needed?
Expand All @@ -60,7 +63,7 @@ export namespace Arg {

type Is = <T>(predicate: PredicateFunction<ExtractFirstArg<T>>) => ReturnArg<ExtractFirstArg<T>>
const isFunction = <T>(predicate: PredicateFunction<ExtractFirstArg<T>>, options?: ArgumentOptions) => new Argument(
`{predicate ${toStringify(predicate)}}`, predicate, options
`Arg.is{${toStringify(predicate)}}`, predicate, options
)
export const is = createInversable(isFunction) as Inversable<Is>

Expand All @@ -79,7 +82,7 @@ export namespace Arg {
type Any = <T extends AnyType = 'any'>(type?: T) => MapAnyReturn<T>

const anyFunction = (type: AnyType = 'any', options?: ArgumentOptions) => {
const description = `{type ${type}}`
const description = `Arg.any{${type}}`
const predicate = (x: any) => {
switch (type) {
case 'any':
Expand Down
Loading