経験は何よりも饒舌

10年後に真価を発揮するかもしれないブログ 

Jestのカバレッジ計測について(モック編)

Jestのカバレッジ計測について - 経験は何よりも饒舌 で、「JestはJestのAPIを用いたテストに対してカバレッジを計測しているのではなく、テストで呼び出されたか否かでカバレッジを計測している」ことが分かったけど一応モックの場合も調査してみる。
モック自体は Jest のモック関数を整理する - 経験は何よりも饒舌 で整理している。

次のコードで調査していく。

const obj = {
  random: function () {
    return Math.random();
  },
  randomPlusOne: function () {
    return Math.random() + 1;
  },
};

module.exports = obj;

まずはモックを使わずにテストしてみる。

const obj = require("./sample");

describe("sample", () => {
  it("random", () => {
    expect(obj.random()).toBe(1);
  });
  it("randomPlusOne", () => {
    expect(obj.randomPlusOne()).toBe(2);
  })
})

予想通りテストは通らないが、カバレッジは100%になる。

$ npm run test

> jest-playground@1.0.0 test
> jest --collectCoverage

 FAIL  ./sample.test.js
  sample
    ✕ random (4 ms)
    ✕ randomPlusOne (1 ms)

  ● sample › random

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

    Expected: 1
    Received: 0.0018626031125137388

       6 | describe("sample", () => {
       7 |   it("random", () => {
    >  8 |     expect(obj.random()).toBe(1);
         |                          ^
       9 |   });
      10 |   it("randomPlusOne", () => {
      11 |     expect(obj.randomPlusOne()).toBe(2);

      at Object.<anonymous> (sample.test.js:8:26)

  ● sample › randomPlusOne

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

    Expected: 2
    Received: 1.732370688737492

       9 |   });
      10 |   it("randomPlusOne", () => {
    > 11 |     expect(obj.randomPlusOne()).toBe(2);
         |                                 ^
      12 |   })
      13 | })
      14 |

      at Object.<anonymous> (sample.test.js:11:33)

-----------|---------|----------|---------|---------|-------------------
File       | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-----------|---------|----------|---------|---------|-------------------
All files  |     100 |      100 |     100 |     100 |                   
 sample.js |     100 |      100 |     100 |     100 |                   
-----------|---------|----------|---------|---------|-------------------
Test Suites: 1 failed, 1 total
Tests:       2 failed, 2 total
Snapshots:   0 total
Time:        0.537 s, estimated 1 s

ここで、一旦モックから離れて、ロジックに全く関与していない次のようなテストを試してみる。

describe("sample", () => {
  it("1+1", () => {
    expect(1 + 1).toBe(2);
  });
})

予想通り、カバレッジは0%になる。

$ npm run test

> jest-playground@1.0.0 test
> jest --collectCoverage

 PASS  ./sample.test.js
  sample
    ✓ 1+1 (2 ms)

----------|---------|----------|---------|---------|-------------------
File      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
----------|---------|----------|---------|---------|-------------------
All files |       0 |        0 |       0 |       0 |                   
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        0.526 s, estimated 1 s
Ran all test suites.

次に、前回のテストにrequireだけしたテストを試してみる。

const obj = require("./sample");

describe("sample", () => {
  it("1+1", () => {
    expect(1 + 1).toBe(2);
  });
})

すると、ロジックに全く関与していないのにrequireしただけでカバレッジは上昇している

$ npm run test

> jest-playground@1.0.0 test
> jest --collectCoverage

 PASS  ./sample.test.js
  sample
    ✓ 1+1 (2 ms)

-----------|---------|----------|---------|---------|-------------------
File       | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-----------|---------|----------|---------|---------|-------------------
All files  |      50 |      100 |       0 |      50 |                   
 sample.js |      50 |      100 |       0 |      50 | 3-6               
-----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        0.433 s, estimated 1 s
Ran all test suites.

次はモックを実装して試してみる。

const obj = require("./sample");
jest.spyOn(obj, 'random').mockImplementation(() => 1);
jest.spyOn(obj, 'randomPlusOne').mockImplementation(() => 2);

describe("sample", () => {
  it("random", () => {
    expect(obj.random()).toBe(1);
  });
  it("randomPlusOne", () => {
    expect(obj.randomPlusOne()).toBe(2);
  })
})

カバレッジの結果は前回と同じであり、つまり、モックはカバレッジに関与していないことがわかる。

$ npm run test

> jest-playground@1.0.0 test
> jest --collectCoverage

 PASS  ./sample.test.js
  sample
    ✓ random (2 ms)
    ✓ randomPlusOne (1 ms)

-----------|---------|----------|---------|---------|-------------------
File       | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-----------|---------|----------|---------|---------|-------------------
All files  |      50 |      100 |       0 |      50 |                   
 sample.js |      50 |      100 |       0 |      50 | 3-6               
-----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        0.571 s, estimated 1 s
Ran all test suites.

次のように、JestのモックではなくSinon.JSを用いてテストしてみる。

const obj = require("./sample");
const sinon = require("sinon");

const randomStub = sinon.stub(obj, "random");
randomStub.returns(1);
const randomPlusOneStub = sinon.stub(obj, "randomPlusOne");
randomPlusOneStub.returns(2);

describe("sample", () => {
  it("random", () => {
    expect(obj.random()).toBe(1);
  });
  it("randomPlusOne", () => {
    expect(obj.randomPlusOne()).toBe(2);
  });
});

カバレッジの結果は前回と同じであり、つまり、モックの方法もテストに関与していないことがわかる。

$ npm run test

> jest-playground@1.0.0 test
> jest --collectCoverage

 PASS  ./sample.test.js
  sample
    ✓ random (2 ms)
    ✓ randomPlusOne

-----------|---------|----------|---------|---------|-------------------
File       | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-----------|---------|----------|---------|---------|-------------------
All files  |      50 |      100 |       0 |      50 |                   
 sample.js |      50 |      100 |       0 |      50 | 3-6               
-----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        0.635 s

まとめ

  • モックの実装やモックの方法はカバレッジに関与していない