阅读前应该具备:
了解vue相关知识 熟悉测试APP 的UI
按照惯例,在Vue的生态中,当您要测试应用程序时,您可以使用@ vue/test-utils-Vue的官方测试库。这个库提供相关API以方便用户测试渲染的Vue组件实例。例如:
复制 // example.spec .js
import { shallowMount } from '@vue/test-utils'
import HelloWorld from '@/components/HelloWorld.vue'
describe( 'HelloWorld.vue' , ( ) => {
it( 'renders props.msg when passed' , ( ) => {
const msg = 'new message'
const wrapper = shallowMount( HelloWorld, {
propsData: { msg }
} )
expect( wrapper.text ( ) ) .toMatch ( msg)
} )
} )
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13.
您可以看到我们正在使用@vue/test-utils库提供的shallowMount函数来载入渲染我们项目开发代码中Vue组件。
上述测试Vue组件的方法的问题在于,用户最终要与DOM进行交互,对于Vue如何渲染UI没有任何了解。相反,一般只会通过文本内容、输入框的标签以及页面上的一些其他可见的视觉线索来寻找对应UI元素是否可见。
一个更好的方法是为你的Vue应用程序编写测试用例,以反映实际用户如何与之交互,例如在结账页面中寻找一个增加产品购买数量的按钮,所以才需要Vue测试库。
Vue测试库是什么? Vue测试库是Vue的一个轻量级测试库,它在@vue/test-utils的基础上提供了轻量级的实用功能。它是根据一个简单的引导原则创建的:
复制 The more your tests resemble the way your software is used, the more confidence they can give you.
— testing-library.com
为什么使用Vue测试库?
你要写的测试用例不需要关注于实现细节,即测试方法的实现方式,而不是它是否产生了所需要的结果。 您想编写针对实际DOM节点而不是渲染的Vue组件的测试。 您想要编写以与用户相同的方式查询DOM的测试。
Vue测试库的原理 Vue测试库通过提供用于查询DOM的功能来发挥作用,就像用户与DOM进行交互的方式一样。这些实用的功能使您可以通过标签文本查找元素,从其文本内容中找到链接和按钮,并断言可以完全访问Vue应用程序。对于无法通过文字内容或标签来查找元素或其它不切实际的情况,Vue测试库提供了一种比较推荐的方法,即通过使用data-testid属性作为标记来查找这些元素。
data- testd属性被添加到您计划在测试中查询的HTML元素中。如
复制 < button data- testid= "checkoutButton" > Check Out</ button>
开始使用Vue测试库 现在,你已经了解了为什么应该使用Vue测试库以及其工作方式,让我们在全新的Vue CLI创建的Vue项目中进行配置。
首先,我们将通过在终端中运行下面的命令创建一个新项目(假设您的机器上安装了Vue CLI)
复制 vue create vue- testing- library- demo
为了运行测试,我们将使用Jest,它是Facebook开发的测试运行程序。Vue CLI具有一个可以轻松配置Jest的插件。让我们添加该插件:
添加完成后,您会发现在package.json中添加了一个新脚本:
复制 "test:unit" : "vue-cli-service test:unit" ,
这个脚本就是用来运行测试用例的。它还在src中添加了一个新的tests文件夹,并在tests文件夹中添加了一个单元文件夹,其中包含一个名为example.spec.js的示例测试文件。基于Jest的配置,当我们运行npm run test:unit时,Jest将在tests目录中查找文件并运行测试文件。让我们运行示例测试文件:
这应该会运行test/unit目录下的example.spec.js测试文件。让我们看看这个文件的内容。
复制 import { shallowMount } from '@vue/test-utils'
import HelloWorld from '@/components/HelloWorld.vue'
describe( 'HelloWorld.vue' , ( ) => {
it( 'renders props.msg when passed' , ( ) => {
const msg = 'new message'
const wrapper = shallowMount( HelloWorld, {
propsData: { msg }
} )
expect( wrapper.text ( ) ) .toMatch ( msg)
} )
} )
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 默认情况下,使用Vue CLI插件安装Jest会安装@vue/test-utils,因此上面的测试文件使用的是@vue/test-utils中的showMount函数。但是我们要使用Vue测试库而不是@vue/test-utils。为此,我们将卸载@vue/test-utils,因为我们将不需要它。
复制 npm uninstall @vue/ test- utils --save-dev
然后,我们安装Vue测试库
复制 npm install @testing-library/vue --save-dev
接着,我们将tests/unit/example.spec.js修改为:
复制 import { render } from '@testing-library/vue'
import HelloWorld from '@/components/HelloWorld.vue'
describe( 'HelloWorld.vue' , ( ) => {
it( 'renders props.msg when passed' , ( ) => {
const msg = 'new message'
const { getByText } = render( HelloWorld, {
props: { msg }
} )
getByText( msg)
} )
} )
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 再次运行测试,应该可以通过。让我们看看我们做了什么:
我们使用Vue Testing Library公开的render函数来渲染HelloWorld组件。render是渲染Vue组件的唯一方法。调用render时,需要传入Vue组件和一个可选的options对象。 然后,我们使用options对象传递HelloWorld组件所需的参数。render将返回一个带有辅助方法的对象来查询DOM,而这些方法之一就是getByText。 最后,我们使用getByText断言DOM中是否存在具有“新消息”文本内容的元素。
到目前为止,您可能已经注意到从考虑测试渲染的Vue组件到用户在DOM中看到的转变。这一转变将使您从用户的角度测试应用程序,而不是将重点放在实现细节上。
我们的演示应用 现在,我们已经确定了如何使用Vue测试库在Vue中完成测试,我们将继续测试演示应用程序。但是首先,我们将充实应用程序的UI。我们的演示应用程序是一个产品的简单结帐页面。我们将测试用户是否可以在结帐前增加产品的数量,他/她是否可以看到产品名称和价格,等等。让我们开始吧。
首先,在components /目录中创建一个名为checkout的新Vue组件,并将以下代码段添加到其中:
复制 < template>
< div class= "checkout" >
< h1> { { product.name } } - < span data- testid= "finalPrice" > ${ { product.price } } </ span></ h1>
< div class= "quantity-wrapper" >
< div>
< label for= "quanity" > Quantity</ label>
< input type= "number" v- model= "quantity" name= "quantity" class= "quantity-input" />
</ div>
< div>
< button @click= "incrementQuantity" class= "quantity-btn" >+</ button>
< button @click= "decrementQuantity" class= "quantity-btn" >-</ button>
</ div>
</ div>
< p> final price - $< span data- testId= "finalPrice" > { { finalPrice } } </ span></ p>
< button @click= "checkout" class= "checkout-btn" > Checkout</ button>
</ div>
</ template>
< script>
export default {
data( ) {
return {
quantity: 1 ,
}
} ,
props: {
product: {
required: true
}
} ,
computed: {
finalPrice( ) {
return this.product .price * this.quantity
}
} ,
methods: {
incrementQuantity( ) {
this.quantity ++ ;
} ,
decrementQuantity( ) {
if ( this.quantity == 1 ) return;
this.quantity --;
} ,
checkout( ) {
}
}
}
</ script>
< style scoped>
.quantity - wrapper {
margin: 2 em auto;
width: 50 % ;
display: flex;
justify- content: center;
}
.quantity - wrapper div {
margin- right: 2 em;
}
.quantity - input {
margin- left: 0.5 em;
}
.quantity - wrapper button {
margin- right: 1 em;
}
button {
cursor: pointer;
}
</ style>
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 再将App.vue改成:
复制 < template>
< div id= "app" >
< check- out : product= "product" />
</ div>
</ template>
< script>
import CheckOut from './components/CheckOut.vue'
export default {
name: 'App' ,
data( ) {
return {
product: {
name: 'Shure Mic SM7B' ,
price: 200 ,
}
}
} ,
components: {
CheckOut
}
}
</ script>
< style>
#app {
font- family: Avenir, Helvetica, Arial, sans- serif;
- webkit- font- smoothing: antialiased;
- moz- osx- font- smoothing: grayscale;
text - align: center;
color: #2c3e50;
margin- top: 60 px;
}
</ style>
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 对于我们的测试用例,我们将测试以下场景:
用户可以看到产品名称吗? 用户能看到产品价格吗? 用户能否增加产品数量? 用户可以减少产品数量吗? 用户可以随着数量的变化实时查看更新的总价吗?
我们的UI界面是非常简约,因为重点在于如何使用Vue测试库进行测试。让我们继续测试Checkout组件。在tests / unit /中创建一个名为checkout.spec.js的新测试文件内容如下:
复制 import { render, fireEvent } from '@testing-library/vue'
import CheckOut from '@/components/CheckOut.vue'
const product = {
name: 'Korg Kronos' ,
price: 1200
}
describe( 'Checkout.vue' , ( ) => {
// tests goes here
} )
我们的第一个测试用例将是检查产品名称是否正常显示。我们将这样做:
复制 it( 'renders product name' , ( ) => {
const { getByText } = render( CheckOut, {
props: { product }
} )
getByText( product.name )
} )
然后,我们将检查产品价格是否正常显示:
复制 it( 'renders product price' , ( ) => {
const { getByText } = render( CheckOut, {
props: { product }
} )
getByText( "$" + product.price )
} )
继续测试Checkout组件,我们将使用getByDisplayValue helper方法测试用户看到的初始数量是否为1:
复制 it( 'renders initial quantity as 1' , ( ) => {
const { getByDisplayValue, getByText } = render( CheckOut, {
props: { product }
} )
getByDisplayValue( 1 )
} )
接下来,我们将检查当用户点击增加产品数量的按钮时,是否增加了产品数量。为此,我们将使用Vue测试库中的fireEvent实用程序触发单击事件。下面是完整的实现
复制 it( 'increments product quantity' , async ( ) => {
const { getByDisplayValue, getByText } = render( CheckOut, {
props: { product }
} )
const incrementQuantityButton = getByText( '+' )
await fireEvent.click ( incrementQuantityButton)
getByDisplayValue( 2 )
} )
当数量为1时,我们将做同样的递减操作-在这种情况下,我们不减少数量。
当数量为2时,让我们编写两个测试用例。
复制 it( 'does not decrement quantity when quanty is 1' , async ( ) => {
const { getByDisplayValue, getByText } = render( CheckOut, {
props: { product }
} )
const decrementQuantityButton = getByText( '-' )
await fireEvent.click ( decrementQuantityButton)
getByDisplayValue( 1 )
} )
it( 'decrement quantity when quantity greater than 1' , async ( ) => {
const { getByDisplayValue, getByText } = render( CheckOut, {
props: { product }
} )
const incrementQuantityButton = getByText( '+' )
const decrementQuantityButton = getByText( '-' )
await fireEvent.click ( incrementQuantityButton)
await fireEvent.click ( decrementQuantityButton)
getByDisplayValue( 1 )
} )
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 最后,我们将测试是否可以点击数量增减来查看最终价格是否正常显示给用户。
在整个测试案例中,您会注意到,我们更加专注于从用户肉眼能看到并与之交互的角度编写测试。以这种方式编写测试可以确保我们测试的是对应用的用户比较重要的东西。
总结 本文介绍的于测试Vue应用程序的库和方法,称为Vue测试库,我们将了解如何设置它以及如何使用它编写Vue组件的测试用例。
您可以在GitHub上找到演示项目 https://github.com/DominusKelvin/vue-testing-library-demo
*原文链接: https://www.smashingmagazine.com/2020/11/vue-applications-vue-testing-library/