「CI整合」基於Jest Mock API對業務邏輯整合測試

weixin_34357887發表於2018-03-15

有時候我們需要不傳送請求就能完成前端的業務邏輯測試,而許多的業務邏輯都會需要呼叫到後端的API介面。那如何能mock我們所需要的data就是一個問題。當我們能有一個良好的測試環境之後,只要保證後端的介面沒有問題,那我們就可以保證業務邏輯也沒有問題。

所以我們對API的整合測試有以下幾個要求

1.不傳送請求,返回本地假資料

2.釋出前通過CI跑unit test,通過則釋出上線

如何實現?

首先一般我們在network部分都會進行封裝,假設在project中封裝瞭如下的請求工具

// http tool
export default function http() {
    // some implement
}
複製程式碼

既然我們不能傳送真實請求,那我們就需要類似能攔截的東西,攔截也可以通過mock代替。於是我們可以通過jest.mock方法來做。

jest.mock

// api/http.js
// real fn
export default function http() {
    console.log('real');
    //...
}
複製程式碼
// api/__mocks__/http.js
// fake fn
export default function http() {
    console.log('fake');
}
複製程式碼
// some.test.js
jest.mock('../http')
import http from '../http'
http() // 這裡log的是fake,而不是real
複製程式碼

這個就是jest.mock的作用。

正事

明白了這個後就好辦了。專案目錄如下:

-- api
    |-- __mockData__
        |-- user.data.js
    |-- __mocks__
        |-- http.js
    |-- __tests__
        |-- user.test.js
    |-- http.js
    |-- profile // profile 業務模組
        |-- user.js // 獲取使用者資訊
複製程式碼

而我們的fake檔案其實主要做的事情就是根據請求url,method,status等,去讀取對應的本地假資料。大致如下。

// ./api/__mocks__/http.js
// 直接讀取本地假資料
let statusCode;
export function setStatus(code) {
  statusCode = code;
}

export default function http({ url = "", data = {}, method = "get" }) {
  return new Promise((resolve, reject) => {
    const lastSlash = url.lastIndexOf("/");
    const module = url.substring(lastSlash + 1);
    const mockData = require(`../__mockData__/${module}.data`).default;
    const result = mockData[`${method.toUpperCase()} ${statusCode}`];

    process.nextTick(
      () => (statusCode === 200 ? resolve(result) : reject(result))
    );
  });
}

複製程式碼

mockData資料夾則就是放我們的假資料,在這我們可以假設定義如下資料結構,來模擬我們的response

// ./api/__mockData__/user.js
export default {
  'GET 200': {
    code: 0,
    msg: 'ok',
    data: {
      username: '二哲',
      age: 18,
    },
  },
  'POST 200': {
    code: 0,
    msg: 'xxx',
  },
  'GET 400': {
    msg: 'invald params',
    code: -1,
  },
  'GET 401': {},
};
複製程式碼

最後看下我們的 unit test 如何寫

// ./api/__test__/user.test.js
jest.mock('../http') // jest 會自動搜尋目錄下的 __mocks__裡的檔案
import http from '../http';

describe('user api test', () => {
    it("user GET should be 200", async () => {
    setStatus(200);

    const result = await http({
      url,
      method: "get"
    });
    expect(result.data.username).toBe("Kodo");
  });

})
複製程式碼

實現了這個有什麼用?

假設/user介面返回得資料可能是這樣

{
    "username": "二哲",
    "age": 18,
}
複製程式碼

而我們前端service層為UI層提供了一個initUserData的方法,initUserData方法裡的操作是當age為18,那就要返回19。

所以我們在Jest則可以直接這樣測試

// ./api/__test__/user.test.js
jest.mock('../http') // jest 會自動搜尋目錄下的 __mocks__裡的檔案
import { setStatus } from './http';
import { initUserData } from '../user'

describe('user api test', () => {
  it("if user age is 18, age should be 19", async () => {
    expect.assertions(1);
    setStatus(200);
    const result = await initUserData();
    // console.log(result);
    expect(result.data.age).toBe(19);
  });
  
  // test catch
  it("initUserData 400", async () => {
    expect.assertions(1);
    setStatus(400);
    const result = await initUserData();
    expect(result.msg).toBe("invald params");
  });
  
})
複製程式碼

這樣我們使用Jest就可以完成對業務邏輯的測試,Unit test在大型專案中非常需要,每當提交一個feature時,可以跑完所有測試,會讓你非常有安全感,極大提升了專案的穩定性。

TIP

真正的方法(http),與mock的方法http,檔案必須同名,然後放在mocks資料夾下即可。如果不同名使用jest.mock()則會失敗。

以上例子都在這 jest-api-test

相關文章