前端測試 JEST

前言: 問了好些前端大神,回覆都說,作好前端測試是個不簡單的事情,特別是對於前端UI部分的測試。antd在UI上,能夠說作得很好,可是看了他們的相關測試,也沒有很完善。.javascript

目前來講,前端測試在service和model層的應用,結合相關資料和本身遇到的坑,給你們分享一下。前端

1、配置

(1) jest的版本選擇("jest": "^20.0.0"):

項目中,用到的 babel等編譯工具不是最新版本(6.0.0+),在當前最新版本24.0.0下,會報編譯工具不合適的錯誤,把版本降至20.0.0,可使用。具體版本,能夠根據項目中版本進行選擇。java

(2) .babelrc 的配置

項目中的預編譯,主要是由babelrc 文件中配置,presets和plugins兩項,這兩項跟項目中,webpack中的配置保持一致node

presets: ['es2015', 'stage-3'],
plugins: ['transform-class-properties',
          'syntax-dynamic-import',
          'transform-runtime'
         ],
複製代碼
(3) jest.config 的配置

若是項目中在node_modules中依賴了未編譯的插件,須要進行下面的設置。不然,在.test用例中,沒法引入依賴,從而會報錯。webpack

transform: {    // 將.js後綴的文件使用babel-jest處理
    "^.+\\.js$": "babel-jest",
  },
  transformIgnorePatterns: // 未編譯的依賴,須要在這邊配置 ["<rootDir>/node_modules/(?!(snk-sse|javascript-state-machine))"],
複製代碼
(4)package 的配置
"jest": "jest --coverage"
複製代碼

運行npm run jest,就會運行帶有.test.js的全部文件web

2、使用

(1)調用模擬接口

在node裏面,沒有實際接口調用,若是測試涉及到接口調用的返回數據,就須要進行模擬數據。下面,介紹其中一種比較簡單的方法:正則表達式

第一步,在 VideoService.js的同級目錄,新建 mocks 文件夾,而後在文件夾裏面,新建 VideoService.js(mock文件夾的文件名必須與的實際文件名一致)npm

// __mocks__/VideoService.js文件,
export default {
  httpQuery() {
    return new Promise((resolve)=>{
      resolve(123123);
    })
  }
}
複製代碼

第二步,在測試test.js文件中,須要加上jest.mock(),RtVideoService.httpChange調用了VideoService.js的接口,這裏會查找__mocks__下面的文件,並使用VideoService模擬的數據數組

jest.mock('../services/VideoService.js'); // 必須存在,要否則會報錯
test('RtVideoService', () => {
  const obj = { 
    newer: 'linwenhuan@sinosafe.com.cn', 
    callback: { error: jest.fn() }
  };
  await RtVideoService.httpChange(null, obj); // 使用了__mocks__下的文件
  expect(obj.callback.next.mock.calls.length).toBe(1);
})

複製代碼
(2)運行單條測試用例

若是測試失敗,第一件要檢查的事就是,當僅運行這條測試時,它是否仍然失敗。 在 Jest 中很容易地只運行一個測試 — — 只需暫時將 test 命令更改成 test.only:bash

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

test('this test will not run', () => { // 此條不會運行
  expect('A').toBe('A');
});
複製代碼
(3)經常使用的斷言

普通匹配器

  • .toBe - toBe 使用 Object.is 來測試是否徹底相等
  • .not - 用來測試相反的用例
  • .toEqual - 若是你想檢查某個對象的值,請改用 toEqual。
test('two plus two is four', () => { 
    expect(2 + 2).toBe(4); 
});
test('object assignment', () => { 
    const data = {one: 1}; 
    data['two'] = 2; 
    expect(data).toEqual({one: 1, two: 2}); 
});
複製代碼

布爾值匹配器

  • toBeNull 只匹配 null
  • toBeUndefined 只匹配 undefined
  • toBeDefined 與 toBeUndefined 相反
  • toBeTruthy 匹配任何 if 語句爲真
  • toBeFalsy 匹配任何 if 語句爲假
test('null', () => {
  const n = null;
  expect(n).toBeNull();
  expect(n).toBeDefined();
  expect(n).not.toBeUndefined();
  expect(n).not.toBeTruthy();
  expect(n).toBeFalsy();
});

test('zero', () => {
  const z = 0;
  expect(z).not.toBeNull();
  expect(z).toBeDefined();
  expect(z).not.toBeUndefined();
  expect(z).not.toBeTruthy();
  expect(z).toBeFalsy();
});
複製代碼

字符串匹配器

  • toMatch - 正則表達式的字符
  • toHaveLength(number) - 判斷一個有長度的對象的長度

數字匹配器

  • .toBeGreaterThan() - 大於
  • .toBeGreaterThanOrEqual() 大於等於
  • .toBeLessThan() - 小於
  • .toBeLessThanOrEqual() - 小於等於
  • .toBeCloseTo() - 浮點數比較

數組匹配器

  • .toContain(item) - 判斷數組是否包含特定子項
  • .toContainEqual(item) - 判斷數組中是否包含一個特定對象

自定義斷言

expect.extend({
  toBeDivisibleBy(received, argument) {
    const pass = received % argument == 0;
    if (pass) {
      return {
        message: () =>
          `expected ${received} not to be divisible by ${argument}`,
        pass: true,
      };
    } else {
      return {
        message: () => `expected ${received} to be divisible by ${argument}`,
        pass: false,
      };
    }
  },
});

test('even and odd numbers', () => {
  expect(100).toBeDivisibleBy(2);
  expect(101).not.toBeDivisibleBy(2);
});
複製代碼
(4)上面有說到模擬接口,其餘模擬方法

一、jest.fn()

// 舉例
test('測試jest.fn()調用', () => {
  let mockFn = jest.fn();
  let result = mockFn(1, 2, 3);

  // 斷言mockFn的執行後返回undefined
  expect(result).toBeUndefined();
  // 斷言mockFn被調用
  expect(mockFn).toBeCalled();
  // 斷言mockFn被調用了一次
  expect(mockFn).toBeCalledTimes(1);
  // 斷言mockFn傳入的參數爲1, 2, 3
  expect(mockFn).toHaveBeenCalledWith(1, 2, 3);
})

// 實際運用
test('answer', () => {
  const fn = { error: jest.fn() };
  ExeService.answer(null, fn);
  expect(fn.error.mock.calls.length).toBe(1); // 測試成功
});
複製代碼

jest.fn()所建立的Mock函數還能夠設置返回值,定義內部實現或返回Promise對象。

test('測試jest.fn()返回固定值', () => {
  let mockFn = jest.fn().mockReturnValue('default');
  // 斷言mockFn執行後返回值爲default
  expect(mockFn()).toBe('default');
})

test('測試jest.fn()內部實現', () => {
  let mockFn = jest.fn((num1, num2) => {
    return num1 * num2;
  })
  // 斷言mockFn執行後返回100
  expect(mockFn(10, 10)).toBe(100);
})

test('測試jest.fn()返回Promise', async () => {
  let mockFn = jest.fn().mockResolvedValue('default');
  let result = await mockFn();
  // 斷言mockFn經過await關鍵字執行後返回值爲default
  expect(result).toBe('default');
  // 斷言mockFn調用後返回的是Promise對象
  expect(Object.prototype.toString.call(mockFn())).toBe("[object Promise]");
})
複製代碼

二、jest.mock(),改變函數的內部實現,上面已經有實際案例,不估重複講述

三、jest.spyOn()

jest.spyOn()方法一樣建立一個mock函數,可是該mock函數不只可以捕獲函數的調用狀況,還能夠正常的執行被spy的函數。實際上,jest.spyOn()是jest.fn()的語法糖,它建立了一個和被spy的函數具備相同內部代碼的mock函數

jest.mock('../services/VideoService.js'); // 必須存在,要否則會報錯
test('RtVideoService', () => {
  const obj = { 
    newer: 'linwenhuan@sinosafe.com.cn', 
    callback: { error: jest.fn() }
  };
  await RtVideoService.httpChange(null, obj); // 使用了__mocks__下的文件
  const spyFn = jest.spyOn(VideoService, 'httpChange');
  expect(spyFn).toHaveBeenCalled();
  expect(obj.callback.next.mock.calls.length).toBe(1);
})
複製代碼

3、覆蓋率檢查

在執行完寫的測試用例後,你可能會想,有什麼指標來講明我寫的測試用例,覆蓋項目全不全?

"test": "jest --coverage"
複製代碼

一、首先,在package的命令上,帶有coverage,這時,執行成功所有用例,會輸出如下結果:

%stmts是語句覆蓋率(statement coverage):是否是每一個語句都執行了?

%Branch分支覆蓋率(branch coverage):是否是每一個if代碼塊都執行了?

%Funcs函數覆蓋率(function coverage):是否是每一個函數都調用了?

%Lines行覆蓋率(line coverage):是否是每一行都執行了?

二、coverage 須要忽略的文件或文件夾

coveragePathIgnorePatterns: [
    "\\\\node_modules\\\\",
    "<rootDir>/src/utils/",
    "<rootDir>/src/observers/",
    "<rootDir>/lib/",
  ],
複製代碼

三、執行完測試用例後,會在項目要目錄,生成coverage文件夾,裏面輸出覆蓋率報告

4、從中獲得的體會

  • 一、爲了進行jest、 Mock,若是代碼不適合測試,會對代碼進行重構,這在必定程度使本身的代碼結構更加趨於合理;
  • 二、單元測試能夠給出每項測試的響應時間,合理劃分的單元測試有助於定位代碼的性能問題;
  • 三、單元測試仍是一份很好的業務文檔,每項測試的描述均可以體現業務邏輯

JEST 官網

相關文章
相關標籤/搜索