本文轉載自:衆成翻譯
譯者:iOSDevLog
連接:http://www.zcfy.cc/article/3806
原文:https://www.fullstackreact.com/30-days-of-react/day-25/html
今天,咱們將看看一個由Airbnb所維護的開源庫,名爲Enzyme,使得測試變得簡單易用。react
昨天咱們使用了react-addons-test-utils
庫來編寫咱們對Timeline
組件的第一個測試。可是, 此庫是至關低級的, 使用起來可能有點麻煩。Enzyme是由 AirBnb 團隊發佈和維護的測試實用程序庫, 它提供了一個更好的、高級的 API 來處理測試中的React組件。npm
咱們在測試咱們的 <Timeline />
組件:api
咱們將使用Enzyme, 使這些測試更容易寫和更可讀。瀏覽器
昨天, 咱們寫了咱們的第一個測試以下:react-router
import React from 'react'; import TestUtils from 'react-addons-test-utils'; import Timeline from '../Timeline'; describe('Timeline', () => { it('wraps content in a div with .notificationsFrame class', () => { const wrapper = TestUtils.renderIntoDocument(<Timeline />); TestUtils .findRenderedDOMComponentWithClass(wrapper, 'notificationsFrame'); }); })
雖然這是可行的, 但它不是世界上最容易閱讀的測試。當用Enzyme咱們重寫它時讓咱們看看這個測試的樣子。app
咱們能夠只測試組件的輸出, 而不是用Enzyme來測試完整的組件樹。將不渲染任何組件的子級。這稱爲 淺 渲染。dom
Enzyme使淺渲染超容易。咱們將使用Enzyme導出的shallow
函數來裝載咱們的組件。函數
讓咱們更新src/components/Timeline/__tests__/Timeline-test.js
文件, 包括從 enzyme
導入shallow
函數:測試
import React from 'react'; import { shallow } from 'enzyme'; describe('Timeline', () => { it('wraps content in a div with .notificationsFrame class', () => { // our tests }); })
react-addons-test-utils
也支持淺渲染。事實上, Enzyme素只是包裝這個函數。雖然昨天咱們沒有使用淺渲染, 但若是咱們使用它看起來像這樣:
> const renderer = ReactTestUtils.createRenderer(); > renderer.render(<Timeline />) > const result = renderer.getRenderOutput(); >
如今, 爲了渲染咱們的組件, 咱們可使用shallow
方法並將結果存儲在一個變量中。而後, 咱們將爲在其虛擬 dom 中渲染的不一樣的React元素 (HTML 或子組件) 查詢 渲染的組件。
整個斷言包括兩行:
import React from 'react'; import { shallow, mount } from 'enzyme'; import Timeline from '../Timeline'; describe('Timeline', () => { let wrapper; it('wraps content in a div with .notificationsFrame class', () => { wrapper = shallow(<Timeline />); expect(wrapper.find('.notificationsFrame').length).toEqual(1); }); it('has a title of Timeline', () => { wrapper = mount(<Timeline />) expect(wrapper.find('.title').text()).toBe("Timeline") }) describe('search button', () => { let search; beforeEach(() => wrapper = mount(<Timeline />)) beforeEach(() => search = wrapper.find('input.searchInput')) it('starts out hidden', () => { expect(search.hasClass('active')).toBeFalsy() }) it('becomes visible after being clicked on', () => { const icon = wrapper.find('.searchIcon') icon.simulate('click') expect(search.hasClass('active')).toBeTruthy() }) }) describe('status updates', () => { it('has 4 status updates at minimum', () => { wrapper = shallow(<Timeline />) expect( wrapper.find('ActivityItem').length ).toBeGreaterThan(3) }) }) })
咱們可使用yarn test
命令 (或 npm test
命令) 同樣的方式運行測試:
yarn test
咱們的測試經過, 而且更易於閱讀和維護。
讓咱們繼續寫斷言, 從咱們昨天開始的假設列表中抽取。咱們將首先構建咱們的測試套件的其他部分, 寫出咱們的describe
和it
塊。咱們將填寫的規格與斷言後:
import React from 'react'; import { shallow } from 'enzyme'; import Timeline from '../Timeline'; describe('Timeline', () => { let wrapper; it('wraps content in a div with .notificationsFrame class', () => { wrapper = shallow(<Timeline />); expect(wrapper.find('.notificationsFrame').length).toEqual(1); }); it('has a title of Timeline') describe('search button', () => { it('starts out hidden') it('becomes visible after being clicked on') }) describe('status updates', () => { it('has 4 status updates at minimum') }) })
若是咱們遵循測試驅動開發 (簡稱 TDD), 咱們將首先編寫這些假設, 而後構建組件以經過這些測試。
讓咱們填寫這些測試, 以便它們經過咱們現有的Timeline
組件。
咱們的標題測試比較簡單。咱們將查找標題元素並確認標題爲Timeline
。
咱們但願標題能夠在 .title
類下使用。所以, 要在規範中使用 .title
類, 咱們只需使用Enzyme所暴露的find
函數便可獲取組件。
由於咱們的Header
組件是 Timeline
組件的子組件, 因此不能使用shallow()
方法。相反, 咱們必須使用Enzyme提供的 mount()
方法。
Shallow? Mount?
shallow()
渲染函數只渲染咱們專門測試的組件, 它不會渲染子元素。相反, 咱們將不得不mount()
組件, 由於子組件Header
不可用的 jsdom, 不然。咱們將在本文的末尾看到更多的Enzyme函數。
如今讓咱們填寫標題描述:
import React from 'react'; import { shallow, mount } from 'enzyme'; import Timeline from '../Timeline'; describe('Timeline', () => { let wrapper; it('wraps content in a div with .notificationsFrame class', () => { wrapper = shallow(<Timeline />); expect(wrapper.find('.notificationsFrame').length).toEqual(1); }); it('has a title of Timeline', () => { wrapper = mount(<Timeline />) // notice the `mount` expect(wrapper.find('.title').text()).toBe("Timeline") }) })
運行咱們的測試, 咱們將看到這兩個指望經過:
接下來, 讓咱們更新咱們的搜索按鈕測試。咱們在這裏有兩個測試, 其中一個要求咱們測試一個交互。Enzyme爲處理相互做用提供了一個很是乾淨的界面。讓咱們來看看如何根據搜索圖標編寫測試。
一樣, 因爲咱們在時間軸中對子元素進行測試, 所以咱們必須mount()
元素。由於咱們要在一個嵌套的describe()
塊中編寫兩個測試, 因此咱們能夠在幫助器以前編寫一個新的 mount()
來爲每一個測試從新建立, 這樣它們是純的。
此外, 咱們還將使用 input.searchInput
元素進行兩個測試, 所以, 讓咱們在前面的幫助器中爲該元素編寫.find()
。
describe('Timeline', () => { let wrapper; // ... describe('search button', () => { let search; beforeEach(() => wrapper = mount(<Timeline />)) beforeEach(() => search = wrapper.find('input.searchInput')) // ... }) })
若要測試是否隱藏了搜索輸入, 咱們只須要知道是否應用了active
類。Enzyme爲咱們提供了一種使用 hasClass()
方法檢測組件是否有類的方法。讓咱們填寫第一個測試, 指望搜索輸入沒有活動類:
describe('Timeline', () => { let wrapper; // ... describe('search button', () => { let search; beforeEach(() => wrapper = mount(<Timeline />)) beforeEach(() => search = wrapper.find('input.searchInput')) it('starts out hidden', () => { expect(search.hasClass('active')).toBeFalsy() }) it('becomes visible after being clicked on') // ... }) })
關於第二個測試的棘手部分是, 咱們須要點擊圖標元素。在咱們看如何作到這一點以前, 讓咱們先找到它。咱們能夠在包裝上的目標經過它的 .searchIcon
類定位到它。
it('becomes visible after being clicked on', () => { const icon = wrapper.find('.searchIcon') })
如今, 咱們有了圖標, 咱們想模擬一個點擊元素。回想一下, onClick()
方法實際上只是瀏覽器事件的門面。即, 單擊一個元素只是一個經過組件冒泡的事件。而不是控制鼠標或調用元素上的click
, 咱們將模擬發生在它上的事件。對咱們來講, 這將是click
事件。
咱們將在icon
上使用simulate()
方法來建立此事件:
it('becomes visible after being clicked on', () => { const icon = wrapper.find('.searchIcon') icon.simulate('click') })
如今咱們能夠設定一個search
組件具備active
類的指望。
it('becomes visible after being clicked on', () => { const icon = wrapper.find('.searchIcon') icon.simulate('click') expect(search.hasClass('active')).toBeTruthy() })
咱們對Timeline
組件的最後指望是至少有四狀態更新。當咱們將這些元素放置在Timeline
組件上時, 咱們能夠 "淺" 渲染組件。此外, 因爲每一個元素都是自定義組件, 所以咱們能夠搜索'ActivityItem'類型的特定組件的列表。
describe('status updates', () => { it('has 4 status updates at minimum', () => { wrapper = shallow(<Timeline />) // ... }) })
如今, 咱們能夠測試ActivityItem
組件列表的長度。咱們將設定咱們的指望, 若是長度至少是4的名單。
describe('status updates', () => { it('has 4 status updates at minimum', () => { wrapper = shallow(<Timeline />) expect( wrapper.find('ActivityItem').length ).toBeGreaterThan(3) }) })
咱們如今的整個測試套件以下所示:
import React from 'react'; import { shallow, mount } from 'enzyme'; import Timeline from '../Timeline'; describe('Timeline', () => { let wrapper; it('wraps content in a div with .notificationsFrame class', () => { wrapper = shallow(<Timeline />); expect(wrapper.find('.notificationsFrame').length).toEqual(1); }); it('has a title of Timeline', () => { wrapper = mount(<Timeline />) expect(wrapper.find('.title').text()).toBe("Timeline") }) describe('search button', () => { let search; beforeEach(() => wrapper = mount(<Timeline />)) beforeEach(() => search = wrapper.find('input.searchInput')) it('starts out hidden', () => { expect(search.hasClass('active')).toBeFalsy() }) it('becomes visible after being clicked on', () => { const icon = wrapper.find('.searchIcon') icon.simulate('click') expect(search.hasClass('active')).toBeTruthy() }) }) describe('status updates', () => { it('has 4 status updates at minimum', () => { wrapper = shallow(<Timeline />) expect( wrapper.find('ActivityItem').length ).toBeGreaterThan(3) }) }) })
find()
處理什麼?在咱們結束今天以前, 咱們應該看看一個Enzyme"渲染的界面 (在咱們的測試中, wrapper
的對象)。Enzyme文檔 太棒了, 因此咱們要保持這個簡短。
基本上, 當咱們使用find()
函數時, 咱們會將它傳遞給一個選擇器, 它將返回一個ShallowWrapper
實例來包裝找到的節點。find()
函數能夠取字符串、函數或對象。
當咱們將字符串傳遞給find()
函數時, 咱們能夠傳遞 CSS 選擇器或組件的 _顯示名稱_。例如:
wrapper.find('div.link'); wrapper.find('Link')
咱們還能夠將它傳遞給組件構造函數, 例如:
import { Link } from 'react-router'; // ... wrapper.find(Link)
最後, 咱們還能夠傳遞對象屬性選擇器對象, 它經過鍵和值來選擇元素。例如:
wrapper.find({to: '/login'});
返回值是一個 ShallowWrapper
, 它是一種ShallowWrapper
類型 (咱們能夠渲染包裝和淺包裝)。這些 Wrapper
實例有一組功能, 咱們可使用這些函數來針對不一樣的子組件, 查看 props
和 state
,的方法, 以及渲染的組件的其餘屬性, 如html()
和text()
。更甚的是, 咱們能夠把這些調用串在一塊兒。
以 <Link />
組件爲例。若是咱們想找到基於全部可用連接的連接類的 HTML, 咱們能夠編寫這樣的測試:
// ... it('displays a link tag with the Login text', () => { link = wrapper .find('Link') .find({to: '/login'}) expect(link.html()) .toBe('<a class="link">Login</a>') });
哦!今天有不少新的信息, 可是看看咱們是如何快速地用Enzyme來編寫後續測試的。閱讀的速度要快得多, 並且更容易辨別實際發生的事情。
明天, 咱們將繼續咱們的測試旅程和經過集成測試測試咱們的應用。