├── .browserslistrc
├── db.json
├── .prettierrc.js
├── babel.config.js
├── public
├── favicon.ico
└── index.html
├── src
├── assets
│ └── logo.png
├── views
│ ├── About.vue
│ └── Home.vue
├── store
│ └── index.js
├── services
│ └── axios.js
├── main.js
├── components
│ ├── MessageContainer.vue
│ ├── AppHeader.vue
│ ├── LoginForm.vue
│ ├── MessageDisplay.vue
│ └── RandomNumber.vue
├── App.vue
└── router
│ └── index.js
├── jest.config.js
├── .gitignore
├── README.md
├── tests
└── unit
│ ├── AppHeader.spec.js
│ ├── MessageContainer.spec.js
│ ├── LoginForm.spec.js
│ ├── RandomNumber.spec.js
│ └── MessageDisplay.spec.js
├── .eslintrc.js
└── package.json
/.browserslistrc:
--------------------------------------------------------------------------------
1 | > 1%
2 | last 2 versions
3 | not dead
4 |
--------------------------------------------------------------------------------
/db.json:
--------------------------------------------------------------------------------
1 | {
2 | "message": { "text": "Hello from the db!" }
3 | }
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | singleQuote: true,
3 | semi: false
4 | }
5 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: ["@vue/cli-plugin-babel/preset"]
3 | };
4 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Code-Pop/Unit-Testing-Vue-3/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Code-Pop/Unit-Testing-Vue-3/HEAD/src/assets/logo.png
--------------------------------------------------------------------------------
/src/views/About.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
This is an about page
4 |
5 |
6 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | preset: "@vue/cli-plugin-unit-jest",
3 | transform: {
4 | "^.+\\.vue$": "vue-jest"
5 | }
6 | };
7 |
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 | import { createStore } from "vuex";
2 |
3 | export default createStore({
4 | state: {},
5 | mutations: {},
6 | actions: {},
7 | modules: {}
8 | });
9 |
--------------------------------------------------------------------------------
/src/services/axios.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 |
3 | export function getMessage() {
4 | return axios.get('http://localhost:3000/message').then(response => {
5 | return response.data
6 | })
7 | }
8 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "vue";
2 | import App from "./App.vue";
3 | import router from "./router";
4 | import store from "./store";
5 |
6 | createApp(App)
7 | .use(store)
8 | .use(router)
9 | .mount("#app");
10 |
--------------------------------------------------------------------------------
/src/views/Home.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |

4 |
5 |
6 |
7 |
13 |
--------------------------------------------------------------------------------
/src/components/MessageContainer.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 |
6 | # local env files
7 | .env.local
8 | .env.*.local
9 |
10 | # Log files
11 | npm-debug.log*
12 | yarn-debug.log*
13 | yarn-error.log*
14 | pnpm-debug.log*
15 |
16 | # Editor directories and files
17 | .idea
18 | .vscode
19 | *.suo
20 | *.ntvs*
21 | *.njsproj
22 | *.sln
23 | *.sw?
24 |
--------------------------------------------------------------------------------
/src/components/AppHeader.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
16 |
17 |
27 |
--------------------------------------------------------------------------------
/src/components/LoginForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
25 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # unit-testing-vue
2 |
3 | ## Project setup
4 | ```
5 | npm install
6 | ```
7 |
8 | ### Compiles and hot-reloads for development
9 | ```
10 | npm run serve
11 | ```
12 |
13 | ### Compiles and minifies for production
14 | ```
15 | npm run build
16 | ```
17 |
18 | ### Run your unit tests
19 | ```
20 | npm run test:unit
21 | ```
22 |
23 | ### Lints and fixes files
24 | ```
25 | npm run lint
26 | ```
27 |
28 | ### Customize configuration
29 | See [Configuration Reference](https://cli.vuejs.org/config/).
30 |
--------------------------------------------------------------------------------
/src/components/MessageDisplay.vue:
--------------------------------------------------------------------------------
1 |
2 | {{ error }}
3 | {{ message.text }}
4 |
5 |
6 |
25 |
--------------------------------------------------------------------------------
/tests/unit/AppHeader.spec.js:
--------------------------------------------------------------------------------
1 | import AppHeader from '@/components/AppHeader'
2 | import { mount } from '@vue/test-utils'
3 |
4 | describe('AppHeader', () => {
5 | test('if user is not logged in, do not show logout button', () => {
6 | const wrapper = mount(AppHeader)
7 | expect(wrapper.find('button').isVisible()).toBe(false)
8 | })
9 |
10 | test('if logged in, show logout button', async () => {
11 | const wrapper = mount(AppHeader)
12 | await wrapper.setData({ loggedIn: true })
13 |
14 | expect(wrapper.find('button').isVisible()).toBe(true)
15 | })
16 | })
17 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | Home |
4 | About
5 |
6 |
7 |
8 |
9 |
31 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | node: true
5 | },
6 | extends: ["plugin:vue/vue3-essential", "eslint:recommended", "@vue/prettier"],
7 | parserOptions: {
8 | parser: "babel-eslint"
9 | },
10 | rules: {
11 | "no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
12 | "no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off"
13 | },
14 | overrides: [
15 | {
16 | files: [
17 | "**/__tests__/*.{j,t}s?(x)",
18 | "**/tests/unit/**/*.spec.{j,t}s?(x)"
19 | ],
20 | env: {
21 | jest: true
22 | }
23 | }
24 | ]
25 | };
26 |
--------------------------------------------------------------------------------
/src/components/RandomNumber.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ randomNumber }}
4 |
5 |
6 |
7 |
32 |
--------------------------------------------------------------------------------
/tests/unit/MessageContainer.spec.js:
--------------------------------------------------------------------------------
1 | import MessageContainer from '@/components/MessageContainer'
2 | import { mount } from '@vue/test-utils'
3 |
4 | describe('MessageContainer', () => {
5 | it('Wraps MessageDisplay component', () => {
6 | const wrapper = mount(MessageContainer, {
7 | global: {
8 | stubs: {
9 | MessageDisplay: {
10 | template: 'Hello from the db!
'
11 | }
12 | }
13 | }
14 | })
15 |
16 | const stubMessage = 'Hello from the db!'
17 | const message = wrapper.find('[data-testid="message"]').text()
18 | expect(message).toEqual(stubMessage)
19 | })
20 | })
21 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | <%= htmlWebpackPlugin.options.title %>
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/router/index.js:
--------------------------------------------------------------------------------
1 | import { createRouter, createWebHistory } from "vue-router";
2 | import Home from "../views/Home.vue";
3 |
4 | const routes = [
5 | {
6 | path: "/",
7 | name: "Home",
8 | component: Home
9 | },
10 | {
11 | path: "/about",
12 | name: "About",
13 | // route level code-splitting
14 | // this generates a separate chunk (about.[hash].js) for this route
15 | // which is lazy-loaded when the route is visited.
16 | component: () =>
17 | import(/* webpackChunkName: "about" */ "../views/About.vue")
18 | }
19 | ];
20 |
21 | const router = createRouter({
22 | history: createWebHistory(process.env.BASE_URL),
23 | routes
24 | });
25 |
26 | export default router;
27 |
--------------------------------------------------------------------------------
/tests/unit/LoginForm.spec.js:
--------------------------------------------------------------------------------
1 | import LoginForm from '@/components/LoginForm.vue'
2 | import { mount } from '@vue/test-utils'
3 |
4 | describe('LoginForm', () => {
5 | it('emits an event with user data payload', () => {
6 | const wrapper = mount(LoginForm)
7 | const input = wrapper.find('input[type="text"]') // Find text input
8 |
9 | input.setValue('Adam Jahr') // Set value for text input
10 | wrapper.trigger('submit') // Simulate form submission
11 |
12 | // Assert event has been emitted
13 | const formSubmittedCalls = wrapper.emitted('formSubmitted')
14 | expect(formSubmittedCalls).toHaveLength(1)
15 |
16 | // Assert payload is correct
17 | const expectedPayload = { name: 'Adam Jahr' }
18 | expect(wrapper.emitted('formSubmitted')[0][0]).toMatchObject(
19 | expectedPayload
20 | )
21 | })
22 | })
23 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "unit-testing-vue",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build",
8 | "test:unit": "vue-cli-service test:unit",
9 | "lint": "vue-cli-service lint"
10 | },
11 | "dependencies": {
12 | "axios": "^0.21.1",
13 | "core-js": "^3.6.5",
14 | "vue": "^3.0.0",
15 | "vue-router": "^4.0.0-0",
16 | "vuex": "^4.0.0-0"
17 | },
18 | "devDependencies": {
19 | "@vue/cli-plugin-babel": "~4.5.0",
20 | "@vue/cli-plugin-eslint": "~4.5.0",
21 | "@vue/cli-plugin-router": "~4.5.0",
22 | "@vue/cli-plugin-unit-jest": "~4.5.0",
23 | "@vue/cli-plugin-vuex": "~4.5.0",
24 | "@vue/cli-service": "~4.5.0",
25 | "@vue/compiler-sfc": "^3.0.0",
26 | "@vue/eslint-config-prettier": "^6.0.0",
27 | "@vue/test-utils": "^2.0.0-0",
28 | "babel-eslint": "^10.1.0",
29 | "eslint": "^6.7.2",
30 | "eslint-plugin-prettier": "^3.1.3",
31 | "eslint-plugin-vue": "^7.0.0-0",
32 | "flush-promises": "^1.0.2",
33 | "prettier": "^1.19.1",
34 | "typescript": "~3.9.3",
35 | "vue-jest": "^5.0.0-0"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/tests/unit/RandomNumber.spec.js:
--------------------------------------------------------------------------------
1 | import RandomNumber from '@/components/RandomNumber'
2 | import { mount } from '@vue/test-utils'
3 |
4 | describe('RandomNumber', () => {
5 | test('By default, randomNumber data value should be 0', () => {
6 | const wrapper = mount(RandomNumber)
7 | expect(wrapper.html()).toContain('0')
8 | })
9 |
10 | test('If button is clicked, randomNumber should be between 1 and 10', async () => {
11 | const wrapper = mount(RandomNumber)
12 | await wrapper.find('button').trigger('click')
13 | const randomNumber = parseInt(wrapper.find('span').text())
14 | expect(randomNumber).toBeGreaterThanOrEqual(1)
15 | expect(randomNumber).toBeLessThanOrEqual(10)
16 | })
17 |
18 | test('If button is clicked, randomNumber should be between 200 and 300', async () => {
19 | const wrapper = mount(RandomNumber, {
20 | props: {
21 | min: 200,
22 | max: 300
23 | }
24 | })
25 | await wrapper.find('button').trigger('click')
26 | const randomNumber = parseInt(wrapper.find('span').text())
27 | expect(randomNumber).toBeGreaterThanOrEqual(200)
28 | expect(randomNumber).toBeLessThanOrEqual(300)
29 | })
30 | })
31 |
--------------------------------------------------------------------------------
/tests/unit/MessageDisplay.spec.js:
--------------------------------------------------------------------------------
1 | import MessageDisplay from '@/components/MessageDisplay'
2 | import { mount } from '@vue/test-utils'
3 | import { getMessage } from '@/services/axios'
4 | import flushPromises from 'flush-promises'
5 |
6 | jest.mock('@/services/axios')
7 |
8 | beforeEach(() => {
9 | jest.clearAllMocks()
10 | })
11 |
12 | describe('MessageDisplay', () => {
13 | it('Calls getMessage once and displays message', async () => {
14 | const mockMessage = 'Hello from the db'
15 | getMessage.mockResolvedValueOnce({ text: mockMessage })
16 | const wrapper = mount(MessageDisplay)
17 |
18 | await flushPromises()
19 | expect(getMessage).toHaveBeenCalledTimes(1)
20 |
21 | const message = wrapper.find('[data-testid="message"]').text()
22 | expect(message).toEqual(mockMessage)
23 | })
24 |
25 | it('Displays an error when getMessage call fails', async () => {
26 | const mockError = 'Oops! Something went wrong.'
27 | getMessage.mockRejectedValueOnce(mockError)
28 | const wrapper = mount(MessageDisplay)
29 |
30 | await flushPromises()
31 | expect(getMessage).toHaveBeenCalledTimes(1)
32 | const displayedError = wrapper.find('[data-testid="message-error"]').text()
33 | expect(displayedError).toEqual(mockError)
34 | })
35 | })
36 |
--------------------------------------------------------------------------------