React16 Jest单元测试 之 Setup and Teardown(作用域、describe和test的执行顺序)

{app.params.name}} {app.params.name}} {app.params.name}}

项目初始化【这里使用之前的项目,节省时间】

项目地址

https://github.com/durban89/webpack4-react16-reactrouter-demo.git
tag:v_1.0.18

 

拉取

git clone https://github.com/durban89/webpack4-react16-reactrouter-demo.git 
cd webpack4-react16-reactrouter-demo
git fetch origin
git checkout v_1.0.18
npm install 

Scoping 作用域

默认情况下,before和after中的代码适用于每个测试模块。

describe可以将测试分组,将多个测分到一个有意义的组里面。

当describe块将测试分组在一起时,before和after中的代码仅适用于describe块内的测试。具体如下

假设有城市数据库和食品数据库。我们分别可以为不同的测试做不同的设置:

const citys = [];
const foods = [];
let time1 = 1;
let time2 = 1;
const isCity = (city) => {
  if (citys.indexOf(city) > -1) {
    return true;
  }
  return false;
};

const isCityAndFood = (cityAndFood) => {
  let hasCity = false;
  let hasFood = false;

  if (citys.indexOf(cityAndFood.city) > -1) {
    hasCity = true;
  }

  if (foods.indexOf(cityAndFood.food) > -1) {
    hasFood = true;
  }

  if (hasCity && hasFood) {
    return true;
  }

  return false;
};
const initCityDatabase = () => new Promise((resolve, reject) => {
  let promise;

  try {
    setTimeout(() => {
      console.log('initCityDatabase time = ', time1);
      if (time1 === 1) {
        citys.push('Shanghai');
      } else if (time1 === 2) {
        citys.push('Chifeng');
      }
      time1 += 1;
      promise = resolve(citys);
    }, 1000);
  } catch (err) {
    return reject(err);
  }

  return promise;
});

const initFoodDatabase = () => new Promise((resolve, reject) => {
  let promise;

  try {
    setTimeout(() => {
      console.log('initFoodDatabase time = ', time2);
      if (time2 === 1) {
        foods.push('Banana');
      } else if (time2 === 2) {
        foods.push('Apple');
      }
      time2 += 1;
      promise = resolve(foods);
    }, 1000);
  } catch (err) {
    return reject(err);
  }

  return promise;
});

beforeEach(() => initCityDatabase());

test('city database has Shanghai', () => {
  expect(isCity('Shanghai')).toBeTruthy();
});

test('city database has Chifeng', () => {
  expect(isCity('Chifeng')).toBeTruthy();
});

describe('matching cities to foods', () => {
  beforeEach(() => initFoodDatabase());

  test('database has Shanghai and Banana', () => {
    expect(isCityAndFood({
      city: 'Shanghai',
      food: 'Banana',
    })).toBe(true);
  });

  test('database has Chifeng and Apple', () => {
    expect(isCityAndFood({
      city: 'Chifeng',
      food: 'Apple',
    })).toBe(true);
  });
});

执行npm test得到类似如下结果

> webpack4_react16_reactrouter4@1.0.0 test /Users/durban/nodejs/webpack-react-demo
> jest --notify --watchman=false

 PASS  src/__tests__/jest_async_promise.test.js (6.41s)
 PASS  src/__tests__/jest_async_await.test.js (6.401s)
 PASS  src/__tests__/jest_setup_describe.js (7.09s)
  ● Console

    console.log src/__tests__/jest_setup_describe.js:35
      initCityDatabase time =  1
    console.log src/__tests__/jest_setup_describe.js:35
      initCityDatabase time =  2
    console.log src/__tests__/jest_setup_describe.js:35
      initCityDatabase time =  3
    console.log src/__tests__/jest_setup_describe.js:56
      initFoodDatabase time =  1
    console.log src/__tests__/jest_setup_describe.js:35
      initCityDatabase time =  4
    console.log src/__tests__/jest_setup_describe.js:56
      initFoodDatabase time =  2

 PASS  src/__tests__/jest_async_callback.test.js
 PASS  src/__tests__/CheckboxWithLabelComponent.test.jsx
 PASS  src/__tests__/jest_common.test.js
 PASS  src/__tests__/sum.test.js
 PASS  src/__tests__/jest_setup_each_onetime.test.js
 PASS  src/__tests__/jest_setup_each_moretime.test.js

Test Suites: 9 passed, 9 total
Tests:       30 passed, 30 total
Snapshots:   0 total
Time:        12.94s
Ran all test suites.

请注意这里

  ● Console

    console.log src/__tests__/jest_setup_describe.js:35
      initCityDatabase time =  1
    console.log src/__tests__/jest_setup_describe.js:35
      initCityDatabase time =  2
    console.log src/__tests__/jest_setup_describe.js:35
      initCityDatabase time =  3
    console.log src/__tests__/jest_setup_describe.js:56
      initFoodDatabase time =  1
    console.log src/__tests__/jest_setup_describe.js:35
      initCityDatabase time =  4
    console.log src/__tests__/jest_setup_describe.js:56
      initFoodDatabase time =  2

initCityDatabase执行了4次,initFoodDatabase执行了2次,这是因为顶级beforeEach在describe块内的beforeEach之前执行。

这可能有助于说明所有钩子的执行顺序。下面来做个比较

beforeAll(() => console.log('1 - beforeAll'));
afterAll(() => console.log('1 - afterAll'));
beforeEach(() => console.log('1 - beforeEach'));
afterEach(() => console.log('1 - afterEach'));
test('', () => console.log('1 - test'));
describe('Scoped / Nested block', () => {
  beforeAll(() => console.log('2 - beforeAll'));
  afterAll(() => console.log('2 - afterAll'));
  beforeEach(() => console.log('2 - beforeEach'));
  afterEach(() => console.log('2 - afterEach'));
  test('', () => console.log('2 - test'));
});

运行npm test得到结果如下

 webpack4_react16_reactrouter4@1.0.0 test /Users/durban/nodejs/webpack-react-demo
> jest --notify --watchman=false

 PASS  src/__tests__/jest_setup_describe_diff.js
  ● Console

    console.log src/__tests__/jest_setup_describe_diff.js:1
      1 - beforeAll
    console.log src/__tests__/jest_setup_describe_diff.js:3
      1 - beforeEach
    console.log src/__tests__/jest_setup_describe_diff.js:5
      1 - test
    console.log src/__tests__/jest_setup_describe_diff.js:4
      1 - afterEach
    console.log src/__tests__/jest_setup_describe_diff.js:7
      2 - beforeAll
    console.log src/__tests__/jest_setup_describe_diff.js:3
      1 - beforeEach
    console.log src/__tests__/jest_setup_describe_diff.js:9
      2 - beforeEach
    console.log src/__tests__/jest_setup_describe_diff.js:11
      2 - test
    console.log src/__tests__/jest_setup_describe_diff.js:10
      2 - afterEach
    console.log src/__tests__/jest_setup_describe_diff.js:4
      1 - afterEach
    console.log src/__tests__/jest_setup_describe_diff.js:8
      2 - afterAll
    console.log src/__tests__/jest_setup_describe_diff.js:2
      1 - afterAll

 PASS  src/__tests__/jest_async_promise.test.js (6.439s)
 PASS  src/__tests__/jest_setup_describe.js (7.13s)
 PASS  src/__tests__/jest_async_await.test.js (6.063s)
 PASS  src/__tests__/jest_async_callback.test.js
 PASS  src/__tests__/jest_common.test.js
 PASS  src/__tests__/CheckboxWithLabelComponent.test.jsx
 PASS  src/__tests__/sum.test.js
 PASS  src/__tests__/jest_setup_each_onetime.test.js
 PASS  src/__tests__/jest_setup_each_moretime.test.js

Test Suites: 10 passed, 10 total
Tests:       32 passed, 32 total
Snapshots:   0 total
Time:        12.892s
Ran all test suites.

可以从这里看出其执行的顺序

  ● Console

    console.log src/__tests__/jest_setup_describe_diff.js:1
      1 - beforeAll
    console.log src/__tests__/jest_setup_describe_diff.js:3
      1 - beforeEach
    console.log src/__tests__/jest_setup_describe_diff.js:5
      1 - test
    console.log src/__tests__/jest_setup_describe_diff.js:4
      1 - afterEach
    console.log src/__tests__/jest_setup_describe_diff.js:7
      2 - beforeAll
    console.log src/__tests__/jest_setup_describe_diff.js:3
      1 - beforeEach
    console.log src/__tests__/jest_setup_describe_diff.js:9
      2 - beforeEach
    console.log src/__tests__/jest_setup_describe_diff.js:11
      2 - test
    console.log src/__tests__/jest_setup_describe_diff.js:10
      2 - afterEach
    console.log src/__tests__/jest_setup_describe_diff.js:4
      1 - afterEach
    console.log src/__tests__/jest_setup_describe_diff.js:8
      2 - afterAll
    console.log src/__tests__/jest_setup_describe_diff.js:2
      1 - afterAll

Order of execution of describe and test blocks - describe和test的执行顺序

在一个测试文件中Jest在执行真实的测试之前先执行所有describe的handlers。

这是在before*和after*的handlers中进行setup和teardown的另一个原因而不是在describe blocks。

一旦describe blocks完成,默认情况下,Jest将按照它们在collection phase遇到的顺序依次运行所有测试,等待每个测试完成并在继续之前进行整理。

理解起来很难,看下下面的例子,考虑下下面的代码猜测下输出的顺序是什么:

describe('outer', () => {
  console.log('describe outer-a');

  describe('describe inner 1', () => {
    console.log('describe inner 1');
    test('test 1', () => {
      console.log('test for describe inner 1');
      expect(true).toEqual(true);
    });
  });

  console.log('describe outer-b');

  test('test 1', () => {
    console.log('test for describe outer');
    expect(true).toEqual(true);
  });

  describe('describe inner 2', () => {
    console.log('describe inner 2');
    test('test for describe inner 2', () => {
      console.log('test for describe inner 2');
      expect(false).toEqual(false);
    });
  });

  console.log('describe outer-c');
});

执行npm test,看下下面这块的输出

  ● Console

    console.log src/__tests__/jest_setup_describe_order.js:2
      describe outer-a
    console.log src/__tests__/jest_setup_describe_order.js:5
      describe inner 1
    console.log src/__tests__/jest_setup_describe_order.js:12
      describe outer-b
    console.log src/__tests__/jest_setup_describe_order.js:20
      describe inner 2
    console.log src/__tests__/jest_setup_describe_order.js:27
      describe outer-c
    console.log src/__tests__/jest_setup_describe_order.js:7
      test for describe inner 1
    console.log src/__tests__/jest_setup_describe_order.js:15
      test for describe outer
    console.log src/__tests__/jest_setup_describe_order.js:22
      test for describe inner 2

从输出中可以看出

describe outer-a
describe inner 1
describe outer-b
describe inner 2
describe outer-c

这几行的输出表示从外到内的执行了describe里面的代码,并没有按照顺序执行测试模块,而是在执行完describe只有,在从上到下的按照顺序执行测试模块,这个顺序要好好理解,对于以后的写测试模块的逻辑非常重要。

建议

如果一个测试失败了,首先要检查的事情应该是当测试单独运行的时候测试是否失败。

在Jest中,只运行一个测试很简单 - 只需暂时将该测试命令更改为test.only,如下

test.only('this will be the only test that runs', () => {
  expect(true).toBe(false);
});

test('this test will not run', () => {
  expect('A').toBe('A');
});

这次换个测试命令,不然每次执行npm test会把之前的也一起执行了,命令如下

npx jest src/__tests__/jest_setup_test_only.js

会得到如下的输出

 FAIL  src/__tests__/jest_setup_test_only.js
  ✕ this will be the only test that runs (10ms)
  ○ skipped 1 test

  ● this will be the only test that runs

    expect(received).toBe(expected) // Object.is equality

    Expected: false
    Received: true

      1 | test.only('this will be the only test that runs', () => {
    > 2 |   expect(true).toBe(false);
        |                ^
      3 | });
      4 |
      5 | test('this test will not run', () => {

      at Object.<anonymous> (src/__tests__/jest_setup_test_only.js:2:16)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 skipped, 2 total
Snapshots:   0 total
Time:        2.344s
Ran all test suites matching /src\/__tests__\/jest_setup_test_only.js/i.

从这里可以看出

✕ this will be the only test that runs (10ms)
  ○ skipped 1 test

有两个测试的但是其中一个被跳过了。

当有一个比较复杂的测试中有一个小的测试总是事变,但是单独运行的时候又是成功的,可能是的原因是不同的测试中的有一些干扰元素干扰了这个测试。可以通过用beforeEach清除一些共享状态来解决这个问题。如果不确定是共享的转状态是否被修改,可以通过加入一些日志来判断。

 

项目地址

https://github.com/durban89/webpack4-react16-reactrouter-demo.git
tag:v_1.0.19

 


版权声明

durban 创作并维护的 Gowhich 博客采用 创作共用保留署名-非商业-禁止演绎4.0国际许可证。

本文首发于 Gowhich 博客( https://www.gowhich.com ),版权所有,侵权必究。

本文永久链接: https://www.gowhich.com/blog/847

comments powered by Disqus