如何更改 Jest 中模拟导入的行为?

问题描述 投票:0回答:8

我在更改 Jest 中模拟模块的行为时遇到问题。我想模拟不同的行为来测试我的代码在这些不同情况下的行为方式。我不知道该怎么做,因为对

jest.mock()
的调用被提升到文件顶部,所以我不能只为每个测试调用
jest.mock()
。有没有一种方法可以更改一次测试的模拟模块的行为?

jest.mock('the-package-to-mock', () => ({
    methodToMock: jest.fn(() => console.log('Hello'))
}));

import * as theThingToTest from './toTest'
import * as types from './types'

it('Test A', () => {
    expect(theThingToTest.someAction().type).toBe(types.SOME_TYPE)
})

it('Test B', () => {
    // I need the-package-to-mock.methodToMock to behave differently here
    expect(theThingToTest.someAction().type).toBe(types.OTHER_TYPE)
})

在内部,正如您可以想象的那样,

theThingToTest.someAction()
导入并使用
the-package-to-mock.methodToMock()

javascript unit-testing jestjs mocking
8个回答
232
投票

您可以使用间谍进行模拟并导入模拟的模块。在您的测试中,您可以使用

mockImplementation
:

设置模拟的行为方式
jest.mock('the-package-to-mock', () => ({
  methodToMock: jest.fn()
}));
import { methodToMock } from 'the-package-to-mock'

it('test1', () => {
  methodToMock.mockImplementation(() => 'someValue')
})

it('test2', () => {
  methodToMock.mockImplementation(() => 'anotherValue')
})

17
投票

我使用以下模式:

'use strict'

const packageToMock = require('../path')

jest.mock('../path')
jest.mock('../../../../../../lib/dmp.db')

beforeEach(() => {
  packageToMock.methodToMock.mockReset()
})

describe('test suite', () => {
  test('test1', () => {
    packageToMock.methodToMock.mockResolvedValue('some value')
    expect(theThingToTest.someAction().type).toBe(types.SOME_TYPE)

  })
  test('test2', () => {
    packageToMock.methodToMock.mockResolvedValue('another value')
    expect(theThingToTest.someAction().type).toBe(types.OTHER_TYPE)
  })
})

说明:

您在测试套件级别模拟您尝试使用的类,确保在每次测试之前重置模拟,并且对于每个测试,您使用mockResolveValue来描述返回模拟时将返回的内容


11
投票

另一种方法是使用 jest.doMock(moduleName,factory,options).

例如

the-package-to-mock.ts

export function methodToMock() {
  return 'real type';
}

toTest.ts

import { methodToMock } from './the-package-to-mock';

export function someAction() {
  return {
    type: methodToMock(),
  };
}

toTest.spec.ts

describe('45006254', () => {
  beforeEach(() => {
    jest.resetModules();
  });
  it('test1', () => {
    jest.doMock('./the-package-to-mock', () => ({
      methodToMock: jest.fn(() => 'type A'),
    }));
    const theThingToTest = require('./toTest');
    expect(theThingToTest.someAction().type).toBe('type A');
  });

  it('test2', () => {
    jest.doMock('./the-package-to-mock', () => ({
      methodToMock: jest.fn(() => 'type B'),
    }));
    const theThingToTest = require('./toTest');
    expect(theThingToTest.someAction().type).toBe('type B');
  });
});

单元测试结果:

 PASS  examples/45006254/toTest.spec.ts
  45006254
    ✓ test1 (2016 ms)
    ✓ test2 (1 ms)

-----------|---------|----------|---------|---------|-------------------
File       | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-----------|---------|----------|---------|---------|-------------------
All files  |     100 |      100 |     100 |     100 |                   
 toTest.ts |     100 |      100 |     100 |     100 |                   
-----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        3.443 s

源代码:https://github.com/mrdulin/jest-v26-codelab/tree/main/examples/45006254


6
投票

spyOn
对我们来说效果最好。参见之前的回答:

https://stackoverflow.com/a/54361996/1708297


1
投票

如何针对不同的测试场景更改模拟函数

在我的场景中,我尝试在

jest.mock
之外定义模拟函数,这将返回有关在定义变量之前尝试访问变量的错误。这是因为现代 Jest 会提升
jest.mock
,以便它可以在导入之前发生。不幸的是,这会让您的
const
let
无法按预期运行,因为代码提升到变量定义之上。有些人说使用
var
来代替,因为它会被提升,但大多数 linter 都会对你大喊大叫,为了避免这种黑客攻击,这就是我想出的:

Jest 延迟模拟导入实例调用示例

这使我们能够处理像

new S3Client()
这样的情况,以便模拟所有新实例,同时也模拟实现。如果您愿意,您可能可以使用类似
jest-mock-extended
的内容来完全模拟实现,而不是显式定义模拟。

问题

此示例将返回以下错误:

eferenceError: Cannot access 'getSignedUrlMock' before initialization

Test File

const sendMock = jest.fn()
const getSignedUrlMock = jest.fn().mockResolvedValue('signedUrl')

jest.mock('@aws-sdk/client-s3', () => {
  return {
    S3Client: jest.fn().mockImplementation(() => ({
      send: sendMock.mockResolvedValue('file'),
    })),
    GetObjectCommand: jest.fn().mockImplementation(() => ({})),
  }
})

jest.mock('@aws-sdk/s3-request-presigner', () => {
  return {
    getSignedUrl: getSignedUrlMock,
  }
})

答案

您必须在回调中推迟调用,如下所示:

getSignedUrl: jest.fn().mockImplementation(() => getSignedUrlMock())

完整示例

我不想留下任何想象,虽然我从实际项目中删除了

some-s3-consumer
,但也不是太遥远。

Test File

import { GetObjectCommand, S3Client } from '@aws-sdk/client-s3'
import { SomeS3Consumer } from './some-s3-consumer'

const sendMock = jest.fn()
const getSignedUrlMock = jest.fn().mockResolvedValue('signedUrl')

jest.mock('@aws-sdk/client-s3', () => {
  return {
    S3Client: jest.fn().mockImplementation(() => ({
      send: sendMock.mockResolvedValue('file'),
    })),
    GetObjectCommand: jest.fn().mockImplementation(() => ({})),
  }
})

jest.mock('@aws-sdk/s3-request-presigner', () => {
  return {
    // This is weird due to hoisting shenanigans
    getSignedUrl: jest.fn().mockImplementation(() => getSignedUrlMock()),
  }
})

describe('S3Service', () => {
  const service = new SomeS3Consumer()

  describe('S3 Client Configuration', () => {
    it('creates a new S3Client with expected region and credentials', () => {
      expect(S3Client).toHaveBeenCalledWith({
        region: 'AWS_REGION',
        credentials: {
          accessKeyId: 'AWS_ACCESS_KEY_ID',
          secretAccessKey: 'AWS_SECRET_ACCESS_KEY',
        },
      })
    })
  })

  describe('#fileExists', () => {
    describe('file exists', () => {
      it('returns true', () => {
        expect(service.fileExists('bucket', 'key')).resolves.toBe(true)
      })

      it('calls S3Client.send with GetObjectCommand', async () => {
        await service.fileExists('bucket', 'key')

        expect(GetObjectCommand).toHaveBeenCalledWith({
          Bucket: 'bucket',
          Key: 'key',
        })
      })
    })

    describe('file does not exist', () => {
      beforeEach(() => {
        sendMock.mockRejectedValue(new Error('file does not exist'))
      })

      afterAll(() => {
        sendMock.mockResolvedValue('file')
      })

      it('returns false', async () => {
        const response = await service.fileExists('bucket', 'key')

        expect(response).toBe(false)
      })
    })
  })

  describe('#getSignedUrl', () => {
    it('calls GetObjectCommand with correct bucket and key', async () => {
      await service.getSignedUrl('bucket', 'key')

      expect(GetObjectCommand).toHaveBeenCalledWith({
        Bucket: 'bucket',
        Key: 'key',
      })
    })

    describe('file exists', () => {
      it('returns the signed url', async () => {
        const response = await service.getSignedUrl('bucket', 'key')

        expect(response).toEqual(ok('signedUrl'))
      })
    })

    describe('file does not exist', () => {
      beforeEach(() => {
        getSignedUrlMock.mockRejectedValue('file does not exist')
      })

      afterAll(() => {
        sendMock.mockResolvedValue('file')
      })

      it('returns an S3ErrorGettingSignedUrl with expected error message', async () => {
        const response = await service.getSignedUrl('bucket', 'key')

        expect(response.val).toStrictEqual(new S3ErrorGettingSignedUrl('file does not exist'))
      })
    })
  })

})

0
投票

Andreas 的答案与函数配合得很好,这是我使用它得出的结论:

// You don't need to put import line after the mock.
import {supportWebGL2} from '../utils/supportWebGL';


// functions inside will be auto-mocked
jest.mock('../utils/supportWebGL');
const mocked_supportWebGL2 = supportWebGL2 as jest.MockedFunction<typeof supportWebGL2>;

// Make sure it return to default between tests.
beforeEach(() => {
  // set the default
  supportWebGL2.mockImplementation(() => true); 
});

it('display help message if no webGL2 support', () => {
  // only for one test
  supportWebGL2.mockImplementation(() => false);

  // ...
});

如果您的模拟模块不是函数,它将无法工作。我无法仅更改一次测试的导出布尔值的模拟:/。我的建议是,重构一个函数,或者制作另一个测试文件。

export const supportWebGL2 = /* () => */ !!window.WebGL2RenderingContext;
// This would give you: TypeError: mockImplementation is not a function

0
投票

只是 jest.mock 默认值。 1.

jest.mock('./hooks/useCustom', () => jest.fn(() => value));
// or
jest.mock('./hooks/useCustom');

  1. 并且在测试中你想要覆盖默认值:
import useCustom from './hooks/useCustom'

it('test smt else', () => {
  useCustom.mockReturnValueOnce(differentValue)
  // with ts: (useCustom as any).mockReturnValueOnce(differentValue)
  // render hook or component
  // expect(...)
})

0
投票
import React from 'react';
import Component from 'component-to-mock';

jest.mock('component-to-mock', () => jest.fn());

describe('Sample test', () => {
  it('first test', () => {
     Component.mockImplementation(({ props }) => <div>first mock</div>);
  });

  it('second test', () => {
     Component.mockImplementation(({ props }) => <div>second mock</div>);
  });
});
© www.soinside.com 2019 - 2024. All rights reserved.