denoland / deno_std

The Deno Standard Library
https://jsr.io/@std
MIT License
2.83k stars 582 forks source link

Mocking exported module function not working #5281

Open devsheva opened 3 days ago

devsheva commented 3 days ago

Describe the bug Mocking an exported function doesn't work, cause the mock is not being called, instead the original one is.

Steps to Reproduce

deno.jsonc

{
  "imports": {
    "@/": "./src/",
    "@bot/": "./src/bot/",
    "@deps": "./deps.ts",
    "@dev_deps": "./dev_deps.ts",
    "@std/assert": "jsr:@std/assert@^0.226.0",
    "@std/dotenv": "jsr:@std/dotenv@^0.224.2",
    "@std/testing": "jsr:@std/testing@^0.225.3"
  },
  "fmt": {
    "lineWidth": 80,
    "include": ["src/**/*.ts"],
    "semiColons": false,
    "singleQuote": true,
    "proseWrap": "preserve",
    "exclude": [
      "./node_modules/",
      "./out/",
      "./package-lock.json",
      "./test/cov_profile"
    ]
  },
  "tasks": {
    "dev": "deno run --watch --allow-read --allow-net --allow-env  src/bot.ts",
    "test": "deno test --watch --allow-read=.env,.env.test --allow-net --allow-env "
  },
  "test": {
    "include": [
      "src/**/*.spec.ts"
    ]
  },
  "exclude": ["dist", "node_modules"],
  "compilerOptions": {
    "lib": ["deno.window", "DOM", "DOM.Iterable", "ESNext"],
    "strict": true,
    "allowJs": true,
    "checkJs": true,
  }
}

my_spec.ts

import { describe, it } from '@std/testing/bdd'

import { assertEquals } from '@std/assert'
import { stub } from '@std/testing/mock'

const myFn = () => 'example'

const _internals = { myFn }

describe.only('example', () => {
  it('returns example', () => {
    using _myFnStub = stub(_internals, 'myFn', () => 'mockedExample')
    assertEquals(myFn(), 'mockedExample')
  })
})

Result

error: AssertionError: Values are not equal.

    [Diff] Actual / Expected

-   example
+   mockedExample

  throw new AssertionError(message);

Expected behavior

It should call the mocked function and return mockedExample.

Environment

iuioiua commented 3 days ago

Can you please make the example as minimal as possible while still being reproducible? Ideally it should be done in a single file. Please also include any missing deno.json file and commands run. It's best that no assumptions are made when trying to troubleshoot your issue.

devsheva commented 3 days ago

@iuioiua I updated the issue description with a reproducible code

iuioiua commented 3 days ago

Thank you. For future reference, this is all we needed to be reproducible:

// deno run <script>
import { assertEquals } from "jsr:@std/assert@^0.226.0";
import { stub } from "jsr:@std/testing@^0.225.3/mock";

const myFn = () => "example";

const _internals = { myFn };

using _myFnStub = stub(_internals, "myFn", () => "mockedExample");
assertEquals(myFn(), "mockedExample");

My understanding is that this is expected behavior. This is because stub() modifies _internals.myFn, not myFn() (which is what you're calling). The correct approach is to call a function that uses _internals.myFn(), as the documentation details.

// deno run <script>
import { assertEquals } from "jsr:@std/assert@^0.226.0";
import { stub } from "jsr:@std/testing@^0.225.3/mock";

const myFn = () => "example";

const _internals = { myFn };

+ function foo() {
+   return _internals.myFn();
+ }

using _myFnStub = stub(_internals, "myFn", () => "mockedExample");
- assertEquals(myFn(), "mockedExample");
+ assertEquals(foo(), "mockedExample");
devsheva commented 3 days ago

I agree with that, but the point is that what If I have a method that calls another method, which calls again another method. Basically I need to mock the entire module or at least the implementation of one specific function of that module.

For example what I want to achieve is this behaviour, since I was using Jest before https://jestjs.io/docs/mock-functions#mocking-modules

iuioiua commented 2 days ago

In most cases, you should only need to mock a single method. Can you please clarify exactly what you're trying to achieve?

devsheva commented 2 days ago

I agree again with you. Have you checked the link I sent just to verify? That's what I'm trying to achieve.

Take this simple example:

You have a module method that uses fetch to call some API. In your test suite, obviously you don't want to hit the real API so you need to mock the fetch API since it's the one doing the real call. This is pretty easy cause you can simply stub the globalThis.fetch by using deno's stub. But what about mocking a module function that is called inside another method that you'll use in your tests?

This last example respects your thought about saying that you should only mock a single method. So how do you mock an internal method that is called by a method you're testing? You'd need to stub implementation of one of those