nodejs / help

:sparkles: Need help with Node.js? File an Issue here. :rocket:
1.45k stars 278 forks source link

Native Test Runner: Mocking packages that are exported as classes #4220

Open willdavies0 opened 1 year ago

willdavies0 commented 1 year ago

Details

I'm trying to write mocks for a class that gets initialised in the file I am testing.

Please see the code example below for a detailed example of the problem. This problem often arises when trying to mock 3rd party packages (eg AWS-SDK) that export there APIs as a class that must be initialised.

Coming from Jest where mocking classes like this is very easy, I feel like I am missing something fundamental in how to use the node test runner.

Any help greatly appreciated!

Node.js version

v20.5.0

Example code

Below there are three 'files'. The Class I wish to mock, a file that creates an instance of the class, and the test file.

ClassToMock.js

class ClassToMock {
  constructor() {
    this.foo = 'bar'
  }

  doSomething = () => {
    return this.foo;
  }
}

export { ClassToMock }

fileToTest.js

import { ClassToMock } from './ClassToMock.js'

const functionToTest = () => {
  const instance = new ClassToMock();
  return instance.doSomething();
}

export { functionToTest }

fileToTest.test.js

import { test, mock } from 'node:test'
import assert from 'node:assert'
import { ClassToMock } from './ClassToMock.js
import { functionToTest } from './fileToTest.js

// mocking a method of the class
mock.method(ClassToMock, 'doSomething', () => 'Hello World')

test('test method mock', () => {
  const result = functionToTest()
  assert.strictEqual(result, 'Hello World') // AssertionError Expected: 'Hello World' Actual: 'bar'
})

// mocking the entire class
mock.fn(ClassToMock, () => {
  return {
    doSomething: () => 'Bye world'
  }
})

test('test instance mock', () => {
    const result = functionToTest()
    assert.strictEqual(result, 'Bye World') // AssertionError. Expected: 'Bye World' Actual: 'bar'
})

Operating system

macOS

Scope

Native Test Runner

Module and version

Not applicable.

preveen-stack commented 12 months ago

cc @nodejs/testing

cjihrig commented 12 months ago

Untested, but I think you want this:

// mocking a method of the class
mock.method(ClassToMock, 'doSomething', () => 'Hello World')

to be this:

// mocking a method of the class
mock.method(ClassToMock.prototype, 'doSomething', () => 'Hello World')

You can see more mocking examples in https://github.com/nodejs/node/blob/main/test/parallel/test-runner-mocking.js.

willdavies0 commented 11 months ago

Thanks a lot for this @cjihrig Are there plans to add these examples to the docs https://nodejs.org/api/test.html?

cjihrig commented 11 months ago

There are no plans that I'm aware of. Documentation PRs are always welcome, although I don't know if it makes sense to document every possibility.

0618 commented 10 months ago

I tried using prototype mentioned above, but got the following error:

TypeError [ERR_INVALID_ARG_VALUE]: The argument 'methodName' must be a method. Received undefined

any other suggestions? @cjihrig

cjihrig commented 10 months ago

@0618 I have no clue what your code looks like so it's difficult to provide suggestions.

xiaosha007 commented 5 months ago

I tried using prototype mentioned above, but got the following error:

TypeError [ERR_INVALID_ARG_VALUE]: The argument 'methodName' must be a method. Received undefined

any other suggestions? @cjihrig

hey i just deal with similar issue, i think is because the method declared inside your class in an arrow function, maybe can change from test = () => {} into function test(){}