marchaos / jest-mock-extended

Type safe mocking extensions for Jest https://www.npmjs.com/package/jest-mock-extended
MIT License
810 stars 56 forks source link

How to mock an external package with deepMock #124

Open ofekisr opened 11 months ago

ofekisr commented 11 months ago

I have a module, let's call it myModule that imports @myScope/myPackage myPackage contains two functions - foo and goo

myModule exports the class 'MyClass' that uses both package functions

when I test MyClass I would like to mock the myPackage with mockDeep of jest-mock-extended in each test, I would like to mock different return value or implementations of foo and goo

I didn't find a way to do so

// myModule.ts
import { foo, goo } from '@myScope/myPackage'

export class MyClass {
   toTest(): string {
      const a = foo().moo().koo() // that is why I want deepMock
      return goo(a + 'test')
   }
}

// myModule.test.ts
import { foo, goo } from '@myScope/myPackage'
import { MyClass } from './myModule'

describe('', () => {
  let myClass: MyClass

  beforeEach(() => {
    myClass = new MyClass()
  })

  it('should return an empty string', async () => {
    goo.mockReturnValue('')
    const result = myClass.toTest()
    expect(result).toEqual('')
  })
})
zomnium commented 10 months ago

Hey!

I was looking into doing something similar, sadly deepMock doesn't seem to support method chaining - which is used here. It would be cool to build something for that. Have you ever looked into that @marchaos?

For now I could recommend you to look into:

When you combine those you can build something that injects the dependencies into your class. That gives you the ability to replace the actual library with a mocked version. That mocked version returns the object itself, so you can do method chaining just like the library.

The downside of doing this manually is that if the library api changes you have to update your mocks as well. Good to keep that in mind. That would make a mockDeep version for method chaining very useful.

I wasn't sure about mocking goo in your example, but I hope you get the concept with the rough code draft below:

// myModule.ts
import type { myPackage } from '@myScope/myPackage'

export class MyClass {
  constructor(private foo, private goo) {}

   toTest(): string {
      const a = foo().moo().koo() // that is why I want deepMock
      return goo(a + 'test')
   }
}

// app.ts
import { MyClass } from 'myModule.ts'
import { foo, goo } from '@myScope/myPackage'

const app = MyClass(foo, goo)

// myModule.test.ts
import { MyClass } from './myModule'
import { goo } from '@myScope/myPackage'

function createFooMock() {
  return () => ({
    moo: jest.fn().mockReturnThis(),
    moo: jest.fn().mockReturnThis(),
  })
}

describe('', () => {
  let myClass: MyClass

  beforeEach(() => {
    myClass = new MyClass(foo(), goo)
  })

  it('should return an empty string', async () => {
    // Doesn't look like you want to mock this one: goo.mockReturnValue('')
    const result = myClass.toTest()
    expect(result).toEqual('')
  })
})