--- tags: 技術文章 --- # 隔離 Side Effect 與 依賴 以下方 `getConfigFromApi` 來看,此功能裡做了兩件事 裡面透過依賴一個 `GetConfig` 的功能,去打一個 api 來取得 config 資料 並將結果賦值給外部的 `store.config` ```javascript= const store = reactive({ config: null }) const id = 'shop123' const getConfigFromApi = async () => { const [res, err] = await GetConfig({ id }) if (err) return console.log(err) store.config = res.config } ``` 可以看到 `getConfigFromApi` 這個功能除了依賴其他功能外,還有 `side-effect` 這使得我們在 `unit test` 的時候,必須要想辦法去 mock 掉相關的依賴 又假如說,如果該功能是寫在像是 `vue 組件` 裡的功能 那我們甚至要為了測試此功能而去將整個組件掛載(mount)起來 但掛載組件又會有更多複雜的依賴需要被 mock 掉 而我們的目的就只是為了測試一個簡單的功能,卻要花這麼多力氣 於是我們可以將 `getConfigFromApi` 改寫成這樣: ```javascript const getConfigFromApi = async (getter, setter) => setter(await getter()) // 使用方式 await getConfigFromApi( async () => await GetConfig({ id }), (result) => { const [res, err] = result if (err) return store.config = res.config } ) // 或是 const getConfig = async () => await GetConfig({ id }) const setConfig = (result) => { const [res, err] = result if (err) return store.config = res.config } await getConfigFromApi(getConfig, setConfig) ``` 我們將原本的兩個行為都改成以參數的方式傳入 這樣我們在寫測試時就可以很容易去 mock 掉 api 請求的行為或是回傳結果 同時我們將原本的 side-effect 隔離開來,因為 side-effect 現在是由外部傳入的,並不是於功能自身內去執行,所以我們在測試時也可以很容易的去 mock 儘管看來這樣的改動會使得我們需要撰寫更多的程式碼,但至少這些程式碼是很容易被測試的,容易被測試就意味著我們更能去保證它們不出錯 因此我們的 `unit test` 就可以這樣寫了: ```javascript // 測試取得資料成功情境 test('get config success', () => { const store = {config: null} // 直接 mock 掉原本 call api 的行為,直接回傳想要測試的情境資料 const getter = async () => [{config: {name: 'test'}}, null] const setter = (result) => { const [res, err] = result if (err) return store.config = res.config } await getConfigFromApi(getter, setter) // 驗證 store.config expect(store.config).toEqual({name: 'test'}) }) // 測試取得資料失敗情境 test('get config error', () => { const store = {config: null} // mock 直接回傳 error const getter = async () => [null, 'error'] const setter = (result) => { const [res, err] = result if (err) return store.config = res.config } await getConfigFromApi(getter, setter) expect(store.config).toEqual(null) }) ```