Jest - Как реструктурировать код, чтобы сделать его тестируемым? - PullRequest
0 голосов
/ 01 ноября 2019

У меня есть следующая функция, для которой я хотел бы добавить два модульных теста, чтобы охватить случаи, когда селектор находится на странице или нет.

async function getPrice(page, url) {
    const priceSelector = '#price';
    if (await page.$(priceSelector)) {
        return page.$eval(priceSelector, elem => elem.innerText);
    }
    return null;
}

page определено в другомфункция:

const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto(url);

Я пытался насмехаться page, чтобы page.$(priceSelector) вернул правдивость или ложь, но безуспешно. Примеры в модуле doc to mock имеют смысл, но так ли это, тестирует ли мой код? Если нет, то как это должно быть структурировано?

1 Ответ

1 голос
/ 01 ноября 2019

Необходимо выполнить рефакторинг только в одном месте, вам лучше извлечь функцию обратного вызова elem => elem.innerText в новую функцию.

Например,

index.ts:

export async function getPrice(page, url) {
  const priceSelector = '#price';
  if (await page.$(priceSelector)) {
    return page.$eval(priceSelector, elem => elem.innerText);
  }
  return null;
}

index.spec.ts:

import { getPrice } from './';

const page = {
  $: jest.fn(),
  $eval: jest.fn()
};

beforeEach(() => {
  jest.resetAllMocks();
});

test('should eval', async () => {
  page.$.mockResolvedValueOnce(true);
  page.$eval.mockReturnValueOnce('dummy data');
  const actualValue = await getPrice(page, 'example.com');
  expect(actualValue).toBe('dummy data');
  expect(page.$).toBeCalledWith('#price');
  expect(page.$eval).toBeCalledWith('#price', expect.any(Function));
});

test('should return null', async () => {
  page.$.mockResolvedValueOnce(false);
  const actualValue = await getPrice(page, 'example.com');
  expect(actualValue).toBeNull();
  expect(page.$).toBeCalledWith('#price');
  expect(page.$eval).not.toBeCalled();
});

Вы можете проверить это следующим образом, но функция обратного вызова не будет протестирована и покрыта.

Результат модульного теста с отчетом о покрытии:

 PASS  src/stackoverflow/58651192/index.spec.ts
  ✓ should eval (6ms)
  ✓ should return null (2ms)

----------|----------|----------|----------|----------|-------------------|
File      |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
----------|----------|----------|----------|----------|-------------------|
All files |    85.71 |      100 |       50 |      100 |                   |
 index.ts |    85.71 |      100 |       50 |      100 |                   |
----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        4.786s, estimated 7s

Если мы извлечем функцию обратного вызова для $eval следующим образом:

export const evalCallback = elem => elem.innerText;

Мы можем легко проверить это:

test('evalCallback', () => {
  const actualValue = evalCallback({ innerText: 'unit test' });
  expect(actualValue).toBe('unit test');
});

Результат модульного теста с 100% покрытия:

 PASS  src/stackoverflow/58651192/index.spec.ts (9.066s)
  ✓ should eval (10ms)
  ✓ should return null (1ms)
  ✓ evalCallback (1ms)

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