In this paper, starting from the Vue application strategy and practice of unit testing 03 – Vue component unit test | Lv Liqing blog
Welcome to zhihu column — the reverse attack of the front end (where JavaScript can be, JavaScript will be.)
Welcome to my blog, Zhihu, GitHub, Nuggets.
Objective of this paper
2.1 In the unit tests of Vue applications, what are the differences in unit tests for different UI components? How fine should the granularity be?
// Given a newbie with basic UT knowledge but no Vue tests 🚶 // When he 🚶 reads and practices the Vue unit test section of this article // Then of course, he can learn how to render Vue components in tests. He can learn how to classify UI components. In particular, the way interactions are testedCopy the code
Componentization and UI testing
Before componentization, we didn’t talk about unit testing UI at all, even at the UI page level. Componenting is not all about reuse, but in many cases it is also about divide-and-conquer, so that we can develop UI pages in pieces and unit test them separately.
Front-end componentization has made UI testing a lot easier, and each component can be reduced to an expression, UI = f(data). This pure function returns just a virtual DOM describing what the UI component should look like, essentially a tree of data structures. Input some application state into this pure function, and the corresponding UI description will be output, without directly manipulating the actual UI elements and without the alleged side effects.
Vue component tree testing
In theory, testing a Vue component should be simple to say in terms of pure functions. At the same time, however, there is a problem with testing the component tree of UI rendering, and as you can see from the diagram below, the complexity inevitably increases as you go higher up the hierarchy. For child components in the bottom, we can easy to render it and test the logic of the correct or not, but for the upper layers of the parent component, usually requires to all child components it contains pre-rendered, even the top of the component needs to apply colours to a drawing gives the whole UI page real DOM node to test it, This is obviously not desirable.
In unit testing, we generally want to focus on the component being tested as a stand-alone unit and avoid making indirect assertions about the behavior of its children. In addition, the entire Render tree can become very large for components with many child components, and repeatedly render all the child components can slow down unit testing.
And according to Two things mentioned in Mike Cohn’s test pyramid:
- Write tests of varying granularity
- The higher the level, the fewer tests you should write
To maintain the pyramid shape, a healthy, fast, and maintainable test mix should look like this: write lots of small, fast unit tests. Write coarse-grained tests in place and few high-level end-to-end tests. Be careful not to turn your tests into ice cream, which is a maintenance nightmare and takes too much time to run through. (Via Test Pyramid Practice — ThoughtWorks Insights)
For Vue component tree, Shallow Rendering (Shallow Rendering) solved the problem, that is to say, when we test for an upper component, can need not render it a child of the component, so don’t have to worry about the performance and behavior of child components, so that you can only to the specific components of logic and Rendering output to test. Vue officially provides @vue/test-utils that allows us to use shallow rendering for testing virtual DOM objects, i.e. instances of Vue.com Ponent.
import { shallowMount } from '@vue/test-utils'
const wrapper = shallowMount(Component)
wrapper.vm // the mounted Vue instance
Copy the code
How Vue components are rendered
Shallow renderingshallowMount(component[, options]) => Wrapper
Shallow rendering is useful when testing a component as a unit to ensure that your tests do not implicitly assert the behavior of child components. ShallowMount returns mounted and Rendered Vue components Wrapper, and rendered Vue components Wrapper, rendered But only the first layer of the component’s DOM structure is rendered, and its nested children are not rendered, making rendering more efficient and unit testing faster.
import { shallowMount } from '@vue/test-utils'
describe('Vue Component shallowMount', () => {
it('should have three <todo /> components', () = > {const wrapper = shallowMount(App)
expect(wrapper.find({ name: 'Todo' })).toHaveLength(3)})}Copy the code
The whole volume renderingmount(component[, options]) => Wrapper
The mount method renders the Vue component and all of its children as real DOM nodes, especially if you rely on real DOM structures that must exist, such as button click events. Full DOM rendering requires a full DOM API at the global level, which means that Vue Test Utils is dependent on the browser environment.
Technically, you can run it in a real browser, but due to the complexity of launching a real browser on different platforms, it is advisable to use JSDOM to run tests in Node in a virtual browser environment. The recommended method of using mount is to rely on a library called Jsdom, which is essentially a Headless browser implemented entirely in JavaScript.
import { mount } from '@vue/test-utils'
describe('Vue Component Mount', () => {
it('should delete Todo when click button', () = > {const wrapper = mount(App)
const todoLength = wrapper.find('li').length
wrapper.find('button.delete').at(0).trigger('click')
expect(wrapper.find('li').length).toEqual(todoLength - 1)})})Copy the code
Static renderingrender(component[, options]) => CheerioWrapper
The Render method renders the Vue component as a static HTML string and returns an instance of Cheerio using a third-party HTML parsing library Cheerio, which is a jquery-like library You can traverse the DOM in Node.js. The Rendered CheerioWrapper returned can be used to analyze the HTML code structure of the final result, with the benefit that its API is basically the same as that of both shallowMount and mount methods.
import { render } from '@vue/test-utils'
describe('Vue Component Render', () => {
it('should not have .todo-done class', () = > {const wrapper = render(App)
expect(wrapper.find('.todo-done').length).toEqual(0)
expect(wrapper.text()).toContain('<div class="todo"></div>')})})Copy the code
Pure string renderingrenderToString(component[, options]) => string
RenderToString is very simple, rendering a component into a corresponding HTML string as the name suggests, and I won’t go into it here.
import { renderedString } from '@vue/test-utils'
describe('Vue Component renderedString', () => {
it('should have .todo class', () = > {const renderedString = renderToString(App)
expect(renderedString).toContain('<div class="todo"></div>')})})Copy the code
Instance Wrapperfind()
Methods and selectors
As you can see from the previous example code, the Wrapper returned by either rendering method has a.find() method that takes a selector argument and returns a corresponding Wrapper object. .findall () returns an array of wrapper objects of the same type, containing all eligible subcomponents. Based on this array of objects, the AT method can return a child component at a specified location. The trigger method is used to simulate triggering behavior on the component.
Selectors in @vue/test-utils can be CSS Selectors (and more complex combinations of relational Selectors are supported), VUE components, or an option object. This makes it easy to specify the node you want to look for in the Wrapper object.
/* CSS Selector */
wrapper.find('.foo') //class syntax
wrapper.find('input') //tag syntax
wrapper.find('#foo') //id syntax
wrapper.find('[foo="bar"]') //attribute syntax
wrapper.find('div:first-of-type') //pseudo selectors
Copy the code
In the following example, we can find the component by reference to the Vue component constructor, while we can also find the component and node based on a subset of the Vue component properties, or by selecting the corresponding element according to $ref.
/* Component Constructor */
import foo from './foo.vue'
const wrapper = shallowMount(app)
expect(wrapper.find(foo).is(foo)).toBe(true)
/* Find Option Object */
const wrapper = appWrapper.find({ name: 'my-button' })
wrapper.trigger('click')
/* Find by refs */
const wrapper = appWrapper.find({ ref: 'myButton' })
wrapper.trigger('click')
Copy the code
Testing UI component interaction behavior
Not only can we use the find method to find DOM elements, we can also use the trigger method to simulate triggering a DOM event on the component, such as Click, Change, and so on. For shallow rendering, event emulation does not propagate as expected in the real world, so we must call it on an actual node where event handlers have been set. In fact, the.trigger() method will trigger the component’s prop based on the simulated event. For example,.trigger(‘click’) actually takes the corresponding clickHandler propsData and calls it.
it('should trigger event when click button', () = > {const clickHandler = jest.fn()
const wrapper = shallowMount(Foo, {
propsData: { clickHandler }
})
wrapper.trigger('click')
expect(clickHandler).toHaveBeenCalled()
})
Copy the code
aboutnextTick
How to do?
Vue asynchronously batches DOM updates that are not in effect to avoid unnecessary re-rendering due to repeated data mutations. This is why in practice we often use vue.nexttick to wait for Vue to complete the actual DOM update after the trigger state changes.
To simplify usage, Vue Test Utils applies all updates synchronously, so you don’t need to use vue.nexttick in tests to wait for DOM updates.
Note: When you need to explicitly modify event loops for operations such as asynchronous callbacks or Promise resolution,nextTick
Is still necessary.
To summarize
Unit tests for Vue components are the cornerstone of the front-end UI test portfolio. Unit tests ensure that each component of the code base (the subject being tested) works as expected, and should be far more numerous in the test portfolio than any other type of test. You don’t have to be at the top of the pyramid to test your UI. You can unit test your UI using an E2E framework like Cypress or Nightwatch, but we’ll talk about that later.
To be continued…
## Unit testing basics
- [X] ### The implications of unit Testing and automation
- [x] ### why Jest
- [x] ### Jest
- [x] ### How do I test asynchronous code?
## Vue unit test
- [x] ### Vue components render
- [x] ### Wrapper
find()
Methods and selectors - [x] ### test UI component interaction behavior
## Vuex unit test
- [] ### CQRS
Redux-like
architecture - [] ### how to unit test Vuex
- [] ### Interaction between Vue components and Vuex Store
## Vue applies the test policy
- [] ### unit testing features and locations
- [] ### unit test concerns
- [] ### apply the test strategy of the test
In this paper, starting from the Vue application strategy and practice of unit testing 03 – Vue component unit test | Lv Liqing blog
Welcome to zhihu column — the reverse attack of the front end (where JavaScript can be, JavaScript will be.)
Welcome to my blog, Zhihu, GitHub, Nuggets.