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