簡介
公司的前端專案的技術棧為 react + ts + Rxjs。react就不多說了,大型專案一般都會採用react,單向資料流比雙向繫結渲染來的更加準確和記憶體的更少消耗(diff)。ts的強型別保證了資料的精準,減少bug。Rxjs則會更好的獲取非同步資料,不受其他元素影響。技術的革新帶來的是程式碼和效率的質變,同時也會對一部分技術帶來衝擊。比如說老牌單元測試框架jest。下面我就描述一下在專案中遇到的jest測試難題和解決過程,後面還有可能遇到新的會再更新到博文中
1. 對axios的mock
相信用jest寫react單元測試的同學少不了要測試帶有axios回撥資料的元件。平時測試過程也較簡單,mock一下axios即可。類似程式碼如下;
jest.mock('axios');
...
axios.get.mockResolvedValue(resp); /// resp為mock後的資料,
複製程式碼
但是如果採用ts了會怎樣?? {% asset_img jest1.png axios %} what???ts的強型別導致程式碼的報錯。於是開始在網上搜尋到以下解決答案: 1.將axios.get 別名。雖然不會報錯,但是any型別很明顯不是我們想要的,違背了ts的初始點
(axios.get as any).mockResolvedValue(resp);
複製程式碼
2.利用jest.mock()的回撥函式來滿足。這裡確實能夠做到一定程度的mock,但是如果使用了axios的攔截器的話,那麼就準備重寫一個axios吧。這種方式不推薦。
jest.mock('axios',() =>{
get:() =>{} // 這裡定義成想要的mock形態
})
複製程式碼
我們無非就是要mock元件內的方法,使元件內的方法傳出我們要模擬的值來做測試。所以我們可以使用jest.spyOn.顧名思義就是間諜我們的方法。
import axios from 'axios';
...
jest.spyOn(axios,'get').mockResolvedValue(resp);
複製程式碼
ts無報錯,又能很好的mock值,一舉多得。如果我們自己寫了服務檔案,那麼可以這麼弄
import * as server from './Server';
...
jest.spyOn(server,'getData').mockResolvedValue(resp);
複製程式碼
2.在Rxjs中斷言
Rxjs是一個操作非同步的庫,裡面有很多操作符,如map、pipe...操作符中我們不要用來斷言,我們一般已經將結果mock掉了,所以直接在訂閱subscribe中斷言即可。但是我們對Rxjs的方法進行mock的時候還是需要注意的
import {of} from 'rxjs';
import * as server from './Server';
...
jest.spyOn(server,'getData').mockResolvedValue(of(resp));//這裡需要用of來傳送mock的資料
複製程式碼
同時記得在訂閱裡的結尾,寫好 done()表示結束。
3.受到withRouter影響
元件讀取路由的引數,方法有很多,最好的莫過於使用‘react-router-dom’的withRouter這個高階元件。但是這又會造成一個很大的問題,比如我們按照平時方式使用enzyme來mount元件
import App from './App';//這裡App元件使用了withRouter
...
const wapper = mount(<App/>);
複製程式碼
然而命令列提醒我們 {% asset_img jest2.png error %} 這是我們就需要使用Router元件包裹一下
import App from './App';//這裡App元件使用了withRouter
import {MemoryRouter} from 'react-router-dom';
...
const wapper = mount(<MemoryRouter><App/></MemoryRouter>);
複製程式碼
此時感覺一切順利,該怎麼測試怎麼測試。可是,如果我們的App元件中用到了componentWillReceiveProps這個生命週期的時候我們會發現,使用enzyme的setProps()方法不能將新的props注入到App的componentWillReceiveProps生命週期中。因為我們用setProps將引數注入到了MemoryRouter中了。於是我們可以封裝一個新的class來進行測試
import App from './App';//這裡App元件使用了withRouter
import {MemoryRouter} from 'react-router-dom';
class testApp extends React.Component<Iprops>{ // 這裡的Iprops型別就是App的Props型別
render(){
return(
<MemoryRouter><App {...this.props}/></MemoryRouter>
)
}
}
...
const wapper = mount(<testApp />);
複製程式碼
這裡就可以看上去比較完美的解決componentWillReceiveProps這裡的傳參問題了。 可是新問題出來了,該怎麼獲取App元件內的state呢?或者說內部props? 我們重新看一下官網描述的mount()方法; {% asset_img jest3.png mount %} 後面還可以帶一個option配置。提供上下文配置,childContextTypes。於是我們找到router 的childContext相關型別,如下: {% asset_img jest4.png routerType %} 於是我們可以稍微整理一下option: (這裡要感謝朱德龍)
const option ={
childContextTypes: {
router: () => void 0, //包裹的router是函式型別
},
context: { // 上下文相關配置
router: {
history: createMemoryHistory(),
route: {
location: {
hash: '',
pathname: '',
search: '',
state: '',
},
match: { params: {}, isExact: false, path: '', url: '' },
}
}
}
}
...
const wapper = mount(<App/>,option);
複製程式碼
現在,就可以各種操作App元件啦。得到state也好,更改props也好,按照enzyme上的文件操作即可。
作者簡介: 張栓,人和未來大資料前端工程師,專注於html/css/js的學習與開發。