差異處

這裏顯示兩個版本的差異處。

連向這個比對檢視

web:reactjs:test:teststaticmethod [2019/03/10 17:15]
tony
web:reactjs:test:teststaticmethod [2023/06/25 09:48]
行 1: 行 1:
-{{tag>​reactjs}} 
-====== React - How to test component with static method? ====== 
-===== Problem ===== 
-在撰寫程式時,我會習慣將同性質的utility method放在同一個class中。以實作Login、Logout的功能來說,我就會建立一個class叫Auth,會包含以下methods:​ 
-<code javascript>​ 
-import React, { Component } from '​react';​ 
  
-const session_token_key = '​session_token';​ 
- 
-export default class Auth extends Component { 
-    ​ 
-    static signIn(username,​ password){ 
-        // check credential 
-        sessionStorage.setItem(session_token_key,​ '​session_info'​);​ 
-    } 
- 
-    static signOut(){ 
-        sessionStorage.clear();​ 
-    } 
- 
-    static checkLogin(){ 
-        if( !sessionStorage.getItem(session_token_key) ) { 
-            throw new Error("​no session info"​);​ 
-        } 
-    } 
-}; 
-</​code>​ 
-而Logout的元件中,則會透過Auth.signOut去執行登出的動作:​ 
-<code javascript>​ 
-import React, { useContext } from '​react';​ 
-import Auth from '​./​Auth'​ 
-import { NavItem, NavLink} from '​reactstrap';​ 
-import { AuthContext }  from '​./​AuthContext';​ 
- 
- 
-function Logout(){ 
-    const [ authState, setAuthState ] = useContext(AuthContext);​ 
- 
-    const signOut = ()=>{ 
-        Auth.signOut();​ 
-        setAuthState(false);​ 
-        window.location.href = '/';​ 
-    }; 
- 
-    return ( 
-        <NavItem aria-label="​logout-nav-item"​ className="​d-md-down-none"​ onClick={signOut}>​ 
-            <NavLink href="#"​ ><i className="​icon-logout"></​i></​NavLink>​ 
-        </​NavItem>​ 
-    ); 
-} 
- 
-export default Logout; 
-</​code>​ 
-在先前針對[[web:​reactjs:​react_hook:​usecontext|useContext]]的使用文章中,參考過別人使用jest mock static的寫法如下:​ 
-<code javascript>​ 
-const auth_do_nothing = jest.fn(); 
-auth_do_nothing.mockImplementation(()=>​{});​ 
-Auth.signOut = auth_do_nothing.bind(Auth);​ 
-</​code>​ 
-但這寫法存在restore的問題,會將狀態延續至下一個testcase中;因此本篇文章將分享透過[[https://​sinonjs.org/​releases/​latest/​|Sinon]]去mock static method並且清除mock狀態的做法。 
-===== How to? ===== 
-以Problem中的Logout為例,我想要模擬使用者點擊Logout並透過Auth.signOut執行登出動作,完整的測試程式碼如下:​ 
-<code javascript>​ 
-import Logout from '​../​Logout'​ 
-import React, { useState, useEffect } from '​react';​ 
-import { AuthContext } from '​../​AuthContext';​ 
-import { render, fireEvent } from '​react-testing-library';​ 
-import Auth from '​../​Auth';​ 
-import sinon from '​sinon';​ 
- 
-let authState = true; 
-function LogoutComp(){ 
-    const [ state, setState ] = useState(true);​ 
-    useEffect(()=>​{ 
-        authState = state; 
-    }); 
-    return <​AuthContext.Provider value={[state,​ setState]}><​Logout/></​AuthContext.Provider>;​ 
-} 
- 
-describe('<​Logout/>',​()=>​{ 
-  let stubSignOut;​ 
-  beforeEach(()=>​{ 
-    stubSignOut = sinon.stub(Auth,​ '​signOut'​);​ 
-  }); 
- 
-  afterEach(()=>​{ 
-    stubSignOut.restore();​ 
-  }); 
- 
-  it('​Test logout',​ () => { 
-    // given 
-    const utils = render(<​LogoutComp/>​);​ 
-    const logout_nav_item = utils.getByLabelText('​logout-nav-item'​);​ 
- 
-    // when 
-    fireEvent.click(logout_nav_item);​ 
- 
-    // then 
-    expect(authState).toBe(false);​ 
-    expect(stubSignOut.called).toBe(true);​ 
-  });  ​ 
-}); 
-</​code>​ 
-在test beforeEach中,我透過sinon.stub去模擬Auth.signOut doNothing的動作,即不給予任何的動作;而在afterEach中,我可以透過stub產生的instance去做restore的動作,讓原本Auth.signOut回復到模擬之前:​ 
-<code javascript>​ 
-  let stubSignOut;​ 
-  beforeEach(()=>​{ 
-    stubSignOut = sinon.stub(Auth,​ '​signOut'​);​ 
-  }); 
- 
-  afterEach(()=>​{ 
-    stubSignOut.restore();​ 
-  }); 
-</​code>​ 
-如果要驗證stub method是否有被呼叫,可以使用下面的做法:​ 
-<code javascript>​ 
-expect(stubSignOut.called).toBe(true);​ 
-</​code>​ 
-假如我是在Login form中想要模擬登入失敗的情形呢?​ 假如我一樣用signOut當範例,我可以在stubSignOut instance上指定要拋出怎樣的例外:​ 
-<code javascript>​ 
-  it('​Test stub with throw',​ ()=> { 
-    stubSignOut.throws("​name",​ "​message"​);​ 
-    try { 
-      Auth.signOut();​ 
-      fail("​should be failed"​);​ 
-    } catch(e) { 
-      expect(e.name).toBe("​name"​);​ 
-      expect(e.message).toBe("​message"​);​ 
-    } 
-    expect(stubSignOut.called).toBe(true);​ 
-  }); 
-</​code>​ 
-假如我要模擬的是回傳值,可以透過以下方式:​ 
-<code javascript>​ 
-  it('​Test stub with return value',​ ()=> { 
-    stubSignOut.returns(true);​ 
-    expect(Auth.signOut()).toBe(true);​ 
-    expect(stubSignOut.called).toBe(true);​ 
-  });  
-</​code>​ 
-假如我要模擬有帶參數的情況呢?​ 以login失敗為例,可以使用withArgs來達到這個需求:​ 
-<code javascript>​ 
-  it('​Test stub with arguments',​ ()=> { 
-    stubSignIn.withArgs('​root',​ '​123456'​).throws("​login failed",​ "wrong password"​);​ 
-    try { 
-      Auth.signIn('​root',​ '​123456'​);​ 
-      fail("​should be failed"​);​ 
-    } catch(e) { 
-      expect(e.name).toBe("​login failed"​);​ 
-      expect(e.message).toBe("​wrong password"​);​ 
-    } 
-    expect(stubSignIn.called).toBe(true);​ 
-  });  
-</​code>​ 
-可以看到sinon stub可以滿足我們基本的需求,而且使用也相當容易。我唯一在意的是它stub方式,需要將method name傳遞進去;假如以後有rename的需求,這會不容易馬上察覺。針對這個問題,如果之後有看到其它方法,會再分享給各位。 
-===== Reference ===== 
-  * [[https://​sinonjs.org/​releases/​v7.2.7/​stubs/​|Stubs - Sinon.JS]] 
-  * [[https://​www.robinwieruch.de/​react-fetching-data/​|How to fetch data in React?]] 
- 
-=====    ===== 
----- 
-\\ 
-~~DISQUS~~