Part 6: 用Jest測試Vue中的Methods中的方法和Mock依賴

Test Methods and Mock Dependencies in Vue.js with Jest

用Jest測試Vue中的Methods中的方法和Mock依賴

Learn how to test methods and cope with mocking module dependencies.
學(xué)習(xí)如何測試方法并處理模擬模塊的依賴關(guān)系。

What should we test in methods? That’s a question that we had when we started doing unit tests. Everything comes down to test what that method do, and just that. This means we need to avoid calls to any dependency, so we’ll need to mock them.
哪些是我們在methods中應(yīng)該測試的呢?在我們剛著手單測的時候是個問題。其實測試方法無非就是測它做了什么。這意味著我們需要避免調(diào)用其它依賴,所以我們需要模擬出來。

Let’s add a onSubmit event to the form in the Form.vue component that we created in the last article:
我們在上文中創(chuàng)建的Form組件中為表單添加一個提交事件:

...
<form action="" @submit.prevent="onSubmit(inputValue)">
...

The .prevent modifier is just a convenient way to call event.preventDefault() in order to don’t reload the page. Now make some modifications to call an api and store the result, by adding a results array to the data and a onSubmit method:
.prevent是調(diào)用event.preventDefault()的語法糖,為的就是不重載頁面?,F(xiàn)在我們可以做些修改,在data中添加一個數(shù)組,methods中添加一個onSubmit方法來調(diào)用一個api接口,然后將返回結(jié)果賦給數(shù)組。

data: () => ({
  inputValue: '',
  results: []
}),
methods: {
  onSubmit(value) {
    axios.get('https://jsonplaceholder.typicode.com/posts?q=' + value).then(results => {
      this.results = results.data
    })
  }
},
...

The method is using axios to perform an HTTP call to the “posts” endpoint of jsonplaceholder, which is just a RESTful API for this kind of examples, and with the q query parameter we can search for posts, using the value provided as parameter.
onSubmit方法運用了axios工具,來對https://jsonplaceholder.typicode.com/posts進行了一次HTTP請求,這只是個api的測試示例,通過url拼接參數(shù),來取得對應(yīng)的返回結(jié)果。

For testing the onSubmit method:

對于測試onSubmit方法來說:

  • We don’t wanna call axios.get actual method

    • 我們不想真正調(diào)用axios的get方法
    • We wanna check it is calling axios (but not the real one) and it returns a promise
      • 我們只是想驗證onSubmit方法調(diào)用了axios,而且axios返回了一個promise對象。
  • That promise callback should set this.results to the promise result

    • 這個promise的回調(diào)函數(shù)應(yīng)該將promise的返回結(jié)果賦值給this.results
    • This is probably one of the hardest things to test, when you have external dependencies plus those return promises that do things inside. What we need to do is to mock the external dependencies.
      • 這次應(yīng)該是我們測試用例中最難的一個了,我們有外部環(huán)境依賴時可以依據(jù)能返回的promise對象們在組件內(nèi)做很多事。我們現(xiàn)在需要做的就是要模擬這些外部依賴。

Mock External Module Dependencies

模擬外部模塊的依賴

Jest provides a really great mocking system that allows you to mock everything in a quite convenient way. You don’t need any extra libraries for that. We have seen already jest.spyOn and jest.fn for spying and creating stub functions, although that’s not enough for this case.
Jest提供給我們一套超級棒的mock系統(tǒng),可以讓我們輕松方便地模擬任何事物。從而不再需要引入其他類庫來做這種事情。我們已經(jīng)見識到了運jest.spyOn和jest.fn方法來監(jiān)測并創(chuàng)建stub函數(shù),然而這些對我們的測試用例來所還不夠。

We need to mock the whole axios module. Here’s where jest.mock comes into the stage. It allow us to easily mock module dependencies by writing at the top of you file:
我們需要模擬整個axios依賴模塊。這里我們就能看到j(luò)est.mock大放光彩了!它可以讓我們輕易模擬依賴的模塊,只需要再文件頭部寫如下代碼:
jest.mock('dependency-path', implementationFunction)

You must know that jest.mock is hoisted, which means it will be placed at the top. So:
有一點要注意,jest.mock需要寫在文件頂部。

jest.mock('something', jest.fn)
import foo from 'bar'
...

Is equivalent to:
以上寫法等同于:

import foo from 'bar'
jest.mock('something', jest.fn) // this will end up above all imports and everything
...

By the date of writing, I still haven’t seen much info about how to do in Jest what we’re gonna do here on the internet. Lucky you don’t have to go through the same struggle.
行文時為止,我始終沒有見過網(wǎng)絡(luò)上有我們接下來要講的知識的相關(guān)文章。你們不必向我當初那樣糾結(jié)了。

Let’s write the mock for axios at the top of the Form.test.js test file, and the corresponding test case:
現(xiàn)在在文件頂部開始模擬axios模塊,并準備相應(yīng)的用例:

jest.mock('axios', () => ({
  get: jest.fn()
}))

import { shallow } from 'vue-test-utils'
import Form from '../src/components/Form'
import axios from 'axios' // axios here is the mock from above!

...

it('Calls axios.get', () => {
  cmp.vm.onSubmit('an')
  expect(axios.get).toBeCalledWith('https://jsonplaceholder.typicode.com/posts?q=an')
})

This is great, we’re indeed mocking axios, so the original axios is not called nor any HTTP call. And, we’re even checking by using toBeCalledWith that it’s been called with the right parameters. But we’re still missing something: we’re not checking that it returns a promise.
這樣太棒了,我們確實模擬了axios,所以真實的axios就不會被調(diào)用來發(fā)起HTTP請求。而且,我們甚至用toBeCalledWith驗證了axios會被傳入?yún)?shù)并調(diào)用。但是我們?nèi)匀贿z漏了某些事情:我們沒有檢查返回的promise。

First we need to make our mocked axios.get method to return a promise. jest.fn accepts a factory function as a parameter, so we can use it to define its implementation:
首先,我們需要我們模擬的axios.get方法來返回一個promise對象,jest.fn接受一個工廠函數(shù)來作為參數(shù),使得我們可以用它來定義執(zhí)行條件:

jest.mock('axios', () => ({
  get: jest.fn(() => Promise.resolve({ data: 3 }))
}))

But still, we cannot access the promise, because we’re not returning it. In testing, is a good practice to return something from a function when possible, it makes testing much easier. Let’s do it then in the onSubmit method of the Form.vue component:
但是如此還不行,我們依然拿不到promise對象,因為我們沒有返回它。在測試中,我們要盡量做到能讓一個函數(shù)返回數(shù)據(jù),這樣可以讓測試更簡單。接下來我們就實際運用它:

onSubmit(value) {
  const getPromise = axios.get('https://jsonplaceholder.typicode.com/posts?q=' + value)

  getPromise.then(results => {
    this.results = results.data
  })

  return getPromise
}

Then we can use the very clean ES2017 async/await syntax in the test to check the promise result:
這樣一來我們就可以用ES6中非常簡潔的async/await、syntax方法,在測試中來檢測promise的結(jié)果:

it('Calls axios.get and checks promise result', async () => {
  const result = await cmp.vm.onSubmit('an')

  expect(result).toEqual({ data: [3] })
  expect(cmp.vm.results).toEqual([3])
  expect(axios.get).toBeCalledWith('https://jsonplaceholder.typicode.com/posts?q=an')
})

You can see that we don’t only check the promise result, but also that the results internal state of the component is updated as expected, by doing expect(cmp.vm.results).toEqual([3]).
你可以看到我們不只是檢查了proise的結(jié)果,還驗證了結(jié)果內(nèi)部的數(shù)據(jù)在組件中已經(jīng)被如期更新,正如斷言表達式expect(cmp.vm.results).toEqual([3]).那樣。

Keep mocks externalized

保證模擬的模塊被隔離

Jest allows us to have all our mocks separated in their own JavaScript file, placing them under a mocks folder, keeping the tests as clean as possible.
Jest允許我們模擬的依賴模塊與真實的模塊代碼隔離,將其放置在mocks文件夾下,保證了依賴的清潔。

So we can take the jest.mock... block from top of the Form.test.js file out to it’s own file:
所以我們可以將 jest.mock... 相關(guān)模擬數(shù)據(jù)放在mocks內(nèi)axios.js中:

// test/__mocks__/axios.js
module.exports = {
  get: jest.fn(() => Promise.resolve({ data: [3] }))
}

Just like this, with no extra effort, Jest automatically applies the mock in all our tests so we don’t have to do anything extra or mocking it in every test manually. Notice the module name must match the file name. If you run the tests again, they should still pass.
這樣依賴,不需要其他的額外模擬操作,Jest自動地就為我們所有的測試應(yīng)用模擬數(shù)據(jù),此后就不用每次手動操作了。注意到我們的模塊名依然與文件名匹配。如果你執(zhí)行測試命令,它們都將完美通過測試。

Keep in mind the modules registry and the mocks state is kept, so if you write another test afterwards, you may get undesired results:
要注意的是模擬的模塊和數(shù)據(jù)的狀態(tài)會一直保持,所以如果你接下來還要寫其他測試用例,那么結(jié)果就不是你想要的了:

it('Calls axios.get', async () => {
  const result = await cmp.vm.onSubmit('an')

  expect(result).toEqual({ data: [3] })
  expect(cmp.vm.results).toEqual([3])
  expect(axios.get).toBeCalledWith('https://jsonplaceholder.typicode.com/posts?q=an')
})

it('Axios should not be called here', () => {
  expect(axios.get).toBeCalledWith('https://jsonplaceholder.typicode.com/posts?q=an')
})

The second test should fail, but it doesn’t! That’s because axios.get was called on the test before.
第二個用例本來應(yīng)該報錯的,卻被通過了測試。這是因為axios.get之前測試中被調(diào)用過了。

For that reason, it’s a good practice to clean the module registry and the mocks, since they’re manipulated by Jest in order to make mocking happen. For that you can add in your beforeEach:
因為這個原因,我們直接清空被注冊的模擬模塊就好了,下面的代碼可以添加在beforeEach函數(shù)里。

beforeEach(() => {
  cmp = shallow(Form)
  jest.resetModules()
  jest.clearAllMocks()
})

That will ensure each test starts with clean mocks and modules, as it should be in unit testing.
這樣就可以確保每個測試用例都會在開始的時候有一個清潔無污染的模擬依賴環(huán)境。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容