1# Unit Testing
2
3The goal of a unit test is to improve code quality and assure future development
4or refactoring does not result in broken builds and functionality. Tests that
5require consistent updating when refactoring code are likely tightly coupled to
6the internals of the component.
7
8> Writing unit tests is a constant struggle between writing enough tests and not
9> writing too many. I call this the unit testing Goldilocks rule—not too many,
10> not too few, but just enough. Thousands of tests for a small application can
11> be as damaging to development time as no tests.
12>
13> -- <cite>Ed Yerburgh, Testing Vue Applications (New York: Manning
14> Publications, 2019)</cite>
15
16## Test Libraries
17
18The OpenBMC Web UI unit test framework uses the Jest test runner and relies on
19the following libraries:
20
21- @vue/cli-plugin-unit-jest
22- @vue/test-utils
23
24## Test specification location and naming conventions
25
26- Create the test files in the /tests/unit directory
27- The naming convention is to replicate the folder and component name
28
29### Examples
30
31- The AppHeader.vue single-file component's (SFC) spec file is named
32  `AppHeader.spec.js`
33- Create a global component like `PageSection.vue` in the `/tests/global`
34  directory with the name `PageSection.spec.js`
35- Create a mixin like BVToastMixin in the `/tests/mixins` directory with the
36  name `BVToastMixin.spec.js` Running Tests
37
38## Running Tests
39
40The `test:unit` script will run all the test suites. Until the integration of
41the test script with the continuous integration tool is complete, it needs to be
42run manually before pushing up code for review. If you are working on fixing a
43test that is failing, follow the guidelines for debugging a failed tests or
44fixing failed snapshot tests.
45
46### Debugging a failed test
47
48The `test:unit:debugger` script will help to debug failing tests using the
49Chrome Developer Tools. To debug a test:
50
511. Add a `debugger` statement in the specifications file
521. Run the unit test in debugger mode
531. Open the Chrome browser and go to `chrome://inspect`
54
55### Fixing failed snapshot tests
56
57The `test:update` script will update snapshot tests. If the UI has changed and
58the snapshot tests are failing, after manually verifying the UI changes, run the
59update script to update the snapshots. Running `test:update` can be dangerous,
60as it will update all snapshot tests.
61
62It is critical to verify all snapshot tests before running the update script.
63The easiest way is to run the unit test in watch mode,
64`npm run test:unit -- --watch` and verify each snapshot.
65
66## Guidelines
67
68- Avoid coupling test code to source code when testing functionality
69  - If test cases fail during refactoring, the test case may be tightly coupled
70    with the application structure.
71- A test should not break if the functionality it tests has not changed
72- To maintain test readability, only pass in the data needed for the test to
73  work in your mock object
74- Avoid the creation of side-effects whenever possible
75- There is no return on investment for testing presentational HTML
76- Use `shallowMount` rather than mount unless child component rendering is
77  required
78- Avoid leaky tests by using `localVue` for all plugin installs, for example,
79  when testing a plugin like Vuex
80
81## Components
82
83### What to test
84
851. Test the function's inputs and outputs
86   - Test only dynamically generated output
87   - Test only output that is part of the component contract
881. Test any side-effects
891. Test correct rendering using a snapshot test
90
91### What not to test
92
931. Don't test third-party functionality
941. Don't test the internals of your components or that specific functions are
95   called. This can lead to unnecessary refactoring.
961. Don't go beyond the input and outputs of the component
971. Don't test the functionality of other libraries
981. Static components do not need unit tests, use snapshot testing
99
100### Strategy
101
1021. Define a component contract that is based upon the component API
1031. Create smaller functions with a specific purpose to make testing easier
1041. Test the component API by writing tests first and then writing code to fix
105   the tests
1061. Add a snapshot test once the presentational layer is validated through manual
107   visual testing
108
109### Snapshot Testing
110
111A snapshot test is a comparison of the code from two different points in time.
112When the view is rendering as expected, a snapshot is taken and when the test
113suite is run, this snapshot is compared to the current code to make sure nothing
114has changed.
115
116This type of testing is good for testing that static content output has not
117changed due to any code updates or refactoring. Too many snapshots can slow down
118development during refactors. Typically, these are written once the UI
119presentational layer is complete and validated.
120
121## Vuex Store
122
123There are two testing strategies for testing a Vuex store, which include testing
124store parts separately or testing a running store instance. Each strategy has
125its pros and cons. Given the size of the store and the number of developers that
126could potentially contribute to the project, the suggested strategy is to
127`test store parts separately`.
128
129### Testing Store Parts Separately
130
131Testing the parts separately is easy since each of the parts is a JavaScript
132function. Store parts to test include `actions`, `getters`, and `mutations`.
133
134#### Actions
135
136Since HTTP calls should never be used in a test, actions require extreme
137mocking. Mocking tests rely on assumptions and can lead to faulty tests.
138
139#### Getters
140
141Getters are JavaScript functions that return an output. These are basic
142functions that may not require testing unless there is getter logic. Any logic
143in a getter should be tested.
144
145#### Mutations
146
147Mutations are JavaScript functions that mutate the store state. These are basic
148functions that may not require testing unless there is mutation logic. Any logic
149in a mutation should be tested.
150
151#### Pros
152
153- Easier to debug
154- Smaller tests
155
156#### Cons
157
158- Requires extreme mocking when testing actions
159- Tightly coupled with implementation details
160- More maintenance required when refactoring
161
162### Testing Store Instance
163
164- Uses mutations and actions as inputs
165- State is the output
166- Requires the use of `localVue` when creating the store to avoid leaky tests
167
168#### Pros
169
170- Avoids mocking and brittle tests
171- Refactoring does not break test unless contract changes
172
173#### Cons
174
175- Debugging is more difficult
176
177## Vue Router
178
179- Our current structure does not warrant testing the vue router
180- If there is logic used for creating `RouteLink` items, we should unit test
181  that functionality, which requires stubbing
182- When testing a vue router, it is important to use localVue
183
184[Vuex Testing](https://vuex.vuejs.org/guide/testing.html)
185
186## Resources
187
188- [Vue Test Utils](https://vue-test-utils.vuejs.org/)
189- [Knowing What To Test — Vue Component Unit Testing](https://vuejsdevelopers.com/2019/08/26/vue-what-to-unit-test-components/)
190- [How to unit test a vuex Store](https://www.dev-tips-and-tricks.com/how-to-unit-test-a-vuex-store)
191