Jest入門文件

MrZx發表於2018-07-11

前端測試 Jest (未完待續)

1 配置環境

  1. node npm install --save-dev jest 需在環境變數中配置
  2. yarn yarn add --dev jest yarn global add jest

2 demo

jest 的工程檔案字尾都是xx.test.js,我們在執行一個jest工程的時候,jest會自動查詢執行的字尾為*.test.js的指令碼進行單元測試。

machers 眾多的匹配判斷器

  1. toBe() 用於檢驗基本資料型別的值是否相等
  2. toEqual() 用於檢驗引用資料型別的值,由於js本身object資料型別的本身特性,引用資料型別對比只是指標的對比,但是需要對比物件的每個值,所以這時候用到的是toEqual()
  3. Truthiness 布林值判斷的匹配器
  • toBeNull 只匹配 null
  • toBeUndefined 只匹配 undefined
  • toBeDefined 與 toBeUndefined 相反
  • toBeTruthy 匹配任何 if 語句為真
  • toBeFalsy 匹配任何 if 語句為假
  1. 數字匹配器 用於判斷數字值之間的對比
  • toBeGreaterThan 大於匹配器
  • toBeGreaterThanOrEqual 大於等於匹配器
  • toBeLessThan 小於匹配器
  • toBeLessThanOrEqual 小於等於匹配器
  • tobe 和 toequal 都是等價功能相同的對於數字
  1. toMatch 字串匹配器 和字串的match相同
  2. toContain 陣列匹配器 用於判斷陣列中是否包含某些值
  3. toThrow 報錯匹配器 用於測試特定的丟擲錯誤,可以判斷報錯語句的文字(支援正則匹配),也可以判斷報錯型別。

非同步程式碼的判斷

js 基於單執行緒非同步的特性,在程式碼中充斥的非同步程式和各種回撥,所以我們在測試非同步程式碼的時候要格外注意。一下是各種非同步的案例

  1. callback 回撥
test('the data is peanut butter', () => {
  function callback(data) {
    expect(data).toBe('peanut butter');
  }

  fetchData(callback);
});
 // 這種情況有問題,大家都知道一個ajax請求是一個事件(由兩個事務傳送和返回組成),當傳送成功和返回成功這個事件就是完成的,就是成功的,這個時候jest就認為ajax事件完成了,所以檢驗會在回撥判斷執行前結束,沒有驗證data資料,所以這是有問題的。
複製程式碼

Jest 提供了相應的方法來done來解決這樣的問題,確保回撥執行的來完成整個test驗證。 使用單個引數呼叫 done,而不是將測試放在一個空引數的函式。Jest會等done回撥函式執行結束後,結束測試。 let's show code

test('the data is peanut butter', done => {
  function callback(data) {
    expect(data).toBe('peanut butter');
    done();
  }

  fetchData(callback);
});
  如果 done()永遠不會呼叫,這個測試將失敗,這也是你所希望發生的。
複製程式碼
  1. Promise 程式碼使用Promise處理非同步程式的話,我們有更簡單的方法進行非同步測試。如果Promise返回的狀態是reject則測試自動失敗。
    test('Promise test', ()=>{
        // 斷言測試
        expect.assertions(1);
        // 一定記得return,否則測試拿不到promise的狀態,測試會在fetchData()完成前完成。
        return fetchData().then(data=>{
            expect(data).tobe('promise')
        })
    })
複製程式碼

如果期望的promise是reject,我們可以通過catch來捕獲我們的錯誤。 請確保新增 expect.assertions 來驗證一定數量的斷言被呼叫。 否則一個fulfilled態的 Promise 不會讓測試失敗。

test('the fetch fails with an error', () => {
  expect.assertions(1);
  return fetchData().catch(e => expect(e).toMatch('error'));
});
複製程式碼
  1. .resolves / .rejects 匹配器 Jest有特殊的處理promise的匹配器,resolves和rejects。使用resolves匹配器處理promise,如果返回的狀態是reject則測試將自動失敗。切記不要丟失return。同理,你用rejects匹配器處理,如果你想得到promise返回是reject但是promise成功了返回一個成功的狀態resolve(fulfilled),則測試結果是失敗的。
 // let's show code~
 // resolves
    test('the data is peanut butter', () => {
      expect.assertions(1);
      return expect(fetchData()).resolves.toBe('peanut butter');
    });
 // rejects
    test('the fetch fails with an error', () => {
      expect.assertions(1);
      return expect(fetchData()).rejects.toMatch('error');
    });
複製程式碼
  1. Async/Await 你也可以使用async/await 處理非同步測試。只需要在test的回撥函式前面新增async關鍵字。
// 提前瞭解async和await的使用方法和場景

test('the data is peanut butter', async () => {
  // 切記新增斷言測試
  expect.assertions(1);
  const data = await fetchData();
  expect(data).toBe('peanut butter');
});

test('the fetch fails with an error', async () => {
  expect.assertions(1);
  // 切記新增斷言測試
  try {
    await fetchData();
  } catch (e) {
    expect(e).toMatch('error');
  }
});
複製程式碼

還可以混合使用.resolves / .rejects和Async/Await

// 因為await本身返回的就是一個promise。所以我們可以在返回的promise的基礎之上繼續測試我們的程式碼。
test('the data is peanut butter', async () => {
  expect.assertions(1);
  await expect(fetchData()).resolves.toBe('peanut butter');
});

test('the fetch fails with an error', async () => {
  expect.assertions(1);
  await expect(fetchData()).rejects.toMatch('error');
});
複製程式碼

#Jest的遍歷 和 Setup 和 Teardown 進行一些重複的test設定的時候,我們可以使用beforeEach和afterEach。假設我們在一個互動資料上需要重複的做一些測試設定。假如測試前呼叫initializeCityDatabase()和測試後呼叫clearCityDatabase()。那我們就可以使用beforeEach和afterEach了。 let's show code~

beforeEach and afterEach

    beforeEach(() => {
      initializeCityDatabase();
    });
    
    afterEach(() => {
      clearCityDatabase();
    });
    
    test('city database has Vienna', () => {
      expect(isCity('Vienna')).toBeTruthy();
    });
    
    test('city database has San Juan', () => {
      expect(isCity('San Juan')).toBeTruthy();
    });
複製程式碼

beforeEach 和 afterEach處理非同步的程式碼,同樣可以採用done和promise,如果initializeCityDatabase()返回的是一個promise我們可以這樣處理他。let's show code

    beforeEach(()=>{
        return initializeCityDatabase(); // return一個promise
    })
複製程式碼

beforeAll and afterAll

Scoping

我們可以通過Jest的describe函式,通過js函數語言程式設計的思想來建立一個作用域,來模組化我們的測試。具體場景就是的通過describe來模組我們的foreach的作用範圍。 let's show code

// 這裡的beforeEach適用於所有的test
beforeEach(() => {
  return initializeCityDatabase();
});

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

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

describe('matching cities to foods', () => {
  // 這裡beforeEach僅僅適用於describe中的測試
  beforeEach(() => {
    return initializeFoodDatabase();
  });

  test('Vienna <3 sausage', () => {
    expect(isValidCityFoodPair('Vienna', 'Wiener Schnitzel')).toBe(true);
  });

  test('San Juan <3 plantains', () => {
    expect(isValidCityFoodPair('San Juan', 'Mofongo')).toBe(true);
  });
});
複製程式碼

既然這麼多beforeEach/afterEach和beforeAll/afterAll,那結合descibe來看看我們的執行順序。相信大家看完以後會想起js的(任務佇列和作用域)。

    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'));
    });
    
    // 1 - beforeAll
    // 1 - beforeEach
    // 1 - test
    // 1 - afterEach
    // 2 - beforeAll
    // 1 - beforeEach
    // 2 - beforeEach
    // 2 - test
    // 2 - afterEach
    // 1 - afterEach
    // 2 - afterAll
    // 1 - afterAll
    
正如結果檢視順序,全域性的會作用到describe內部。 注意執行順序,beforeALL和afterAll只會執行一次, 全域性的beforeEach作用到了describe中的test,還要注意的是after作用域中的after比全域性的after先執行。
beforeAll 在beforeEach前先執行,而afterAll在afterEach後執行。
複製程式碼

如果你想在眾多的test中,僅僅想進行其中的一個test,很簡單,你只要在他的test語句中新增.only標識就可以了,其他的測試則會跳過了.let's show code

// 只有這個test會跑
test.only('this will be the only test that runs', () => {
  expect(true).toBe(false);
});

// 這個test則會跳過
test('this test will not run', () => {
  expect('A').toBe('A');
});
複製程式碼

相關文章