---
# System prepended metadata

title: 隔離 Side Effect 與 依賴
tags: [技術文章]

---

---
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)
})
```
