Visually tests for Vue.js Storybook

Visually tests for Vue.js Storybook

Over the last couple of weeks, I've found new joy with writing my Vue.js components within Storybook as a tool to visualise all the possible permutations of a given Component in isolation from the target application. It's all fair game writing...

Over the last couple of weeks, I've found new joy with writing my Vue.js components within Storybook as a tool to visualise all the possible permutations of a given Component in isolation from the target application.

It's all fair game writing your code, hitting save and seeing the change in the browser and visually observing everything works as expected. That's not good enough! I want unit-tests to ensure my components functionality is what I expect.

In this guide, I'll show you how to install Jest to your Storybook project and examples of tests for Vue.js components.

Getting started

If you already have Storybook and Vue.js installed to your project, please skip to Installing Jest

Let's get you quickly started with Storybook and Vue.js by creating a new project folder where your stories will reside.

Make a new folder; here we'll call it design-system but you can call it whatever you like.

mk ./design-system
cd ./design-system

Now we'll install our main dependencies Vue.js and Storybook.

note: My personal preference is the Single File Component style of Vue.js for ease of understanding between projects.

npm init -y # initialize a new package.json quicly
npm install --save vue
npm install --save-dev vue-loader vue-template-compiler @babel/core [email protected]^7.0.0-bridge.0 babel-loader babel-preset-vue
npx -p @storybook/cli sb init --type sfc_vue

Hooray! We've got Storybook installed with a couple of Vue.js examples to start.

Let's boot the Storybook server and see what we got.

npm run storybook

That is great and all, but now we'll want to set up Jest.

Installing Jest

Let's get stuck right in and install all the dependencies required.

npm install --save-dev jest vue-jest babel-jest @babel/core @babel/preset-env @vue/test-utils

Configure Babel by creating a babel.config.js file in the root of the project.

// babel.config.js
module.exports = {
  presets: [
    '@babel/preset-env'
  ]
}

Configuration for Jest will need to be added too by creating a jest.config.js file in the root of the project.

// jest.config.js
module.exports = {
  moduleFileExtensions: ['js', 'vue', 'json'],
  transform: {
    '^.+\\.js/div>: 'babel-jest',
    '.*\\.(vue)/div>: 'vue-jest'
  },
  collectCoverage: true,
  collectCoverageFrom: ['/src/**/*.vue'],
  transformIgnorePatterns: ["/node_modules/([email protected]/runtime)"],
  coverageReporters: ["text-summary", "html", "lcov", "clover"]
}

Finally, we'll need to update the package.json scripts to reference Jest as our test runner.

// package.json
{
  "name": "storybook-vue",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "jest",
    "storybook": "start-storybook -p 6006",
    "build-storybook": "build-storybook"
  },
  ...
}

Before we continue, let's give our installation a quick run to ensure everything is looking ok.

We'll have to run Jest with --passWithNoTests as we haven't written any tests yet.

note: the double dashes -- on their own are intentional to allow the arguments to be passed through to the inner command.

npm run test -- --passWithNoTests

We should see the following output.

npm run test -- --passWithNoTests

> [email protected] test ~/code/design-system
> jest "--passWithNoTests"

No tests found, exiting with code 0

=============================== Coverage summary ===============================
Statements   : Unknown% ( 0/0 )
Branches     : Unknown% ( 0/0 )
Functions    : Unknown% ( 0/0 )
Lines        : Unknown% ( 0/0 )
================================================================================

Great!, everything looks like it's wired up ok for Jest to be happy, now let's write some tests.

Writing our first test

Given we set up the project fresh and ran the initialise command in Storybook, we should have some simple example stories waiting for us in src/stories.

For example, our project structure would look something like this.

tree -I 'node_modules|coverage'
.
|-- babel.config.js
|-- jest.config.js
|-- package-lock.json
|-- package.json
`-- src
    `-- stories
        |-- 0-Welcome.stories.js
        |-- 1-Button.stories.js
        |-- MyButton.vue
        `-- Welcome.vue

2 directories, 8 files

Create a new file in the src/stories directory called MyButton.test.js so we can write our first tests for MyButton.vue.

In this test file, we'll import the MyButton.vue component and @vue/test-utils.

// src/stories/MyButton.test.js
import Component from './MyButton.vue';
import { shallowMount } from "@vue/test-utils";

describe('MyButton', () => {
  let vm
  let wrapper
  beforeEach(() => {
    wrapper = shallowMount(Component)
    vm = wrapper.vm
  })
})

Looking at our MyButton.vue file, we'll see in the `` block a method called onClick.

// src/stories/MyButton.vue (fragment)
export default {
  name: 'my-button',

  methods: {
    onClick () {
      this.$emit('click');
    }
  }
}

This method, when called, will emit a click event to any parent consuming components. So testing this will require us to spy on $emit, and we will expect $emit to be called with click.

Our test will look like the following.

// src/stories/MyButton.test.js (fragment)
describe('onClick', () => {
  it('emits click', () => {
    vm.$emit = jest.fn()
    vm.onClick()
    expect(vm.$emit).toHaveBeenCalledWith('click')
  })
})

Here's a full example of our MyButton.vue.js test file.

// src/stories/MyButton.test.js
import { shallowMount } from "@vue/test-utils";
import Component from './MyButton.vue';

describe('MyButton', () => {
  let vm
  let wrapper
  beforeEach(() => {
    wrapper = shallowMount(Component)
    vm = wrapper.vm
  })

  describe('onClick', () => {
    it('emits click', () => {
      vm.$emit = jest.fn()
      vm.onClick()
      expect(vm.$emit).toHaveBeenCalledWith('click')
    })
  })
})

Brilliant! We can run our tests and see how we're doing.

npm run test

> [email protected] test ~/code/design-system
> jest

 PASS  src/stories/MyButton.test.js
  MyButton
    onClick
      ✓ emits click (15ms)

=============================== Coverage summary ===============================
Statements   : 25% ( 1/4 )
Branches     : 100% ( 0/0 )
Functions    : 33.33% ( 1/3 )
Lines        : 25% ( 1/4 )
================================================================================
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        2.921s
Ran all test suites.

Congratulations you've just written our first test for our Storybook project!

... but what is that in the Coverage summary? 25% of the lines are covered? That has to be improved.

Improving code coverage

As we did with our first test, we'll create a new file for the other component Welcome.test.js in the src/stories directory.

The contents of Welcome.vue is a little more involved with props and having to preventDefault.

// src/stories/Welcome.vue
const log = () => console.log('Welcome to storybook!')

export default {
  name: 'welcome',

  props: {
    showApp: {
      type: Function,
      default: log
    }
  },

  methods: {
    onClick (event) {
      event.preventDefault()
      this.showApp()
    }
  }
}

Let's cover the natural part first, methods as with the tests in MyButton.test.js we can copy most of this code across.

As our code stipulates, we'll need to spy on the given property showApp to ensure it is called and the event we provide will have to include preventDefault.

// src/stories/Welcome.test.js (fragment)
describe('onClick', () => {
  it('calls showApp', () => {
    let showApp = jest.fn()
    wrapper.setProps({
      showApp
    })
    let event = {
      preventDefault: jest.fn()
    }
    vm.onClick(event)
    expect(showApp).toHaveBeenCalled()
    expect(event.preventDefault).toHaveBeenCalled()
  })
})

Testing props have a subtle difference to it as we need to fully mount the component to access the $options where props are defined.

// src/stories/Welcome.test.js (fragment)
describe("props.showApp", () => {
  it('logs message', () => {
    wrapper = mount(Component)
    vm = wrapper.vm
    let prop = vm.$options.props.showApp;

    let spy = jest.spyOn(console, 'log').mockImplementation()
    prop.default()
    expect(console.log).toHaveBeenCalledWith('Welcome to storybook!')
    spy.mockRestore()
  })
})

Ensure to import mount from @vue/test-utils

// src/stories/Welcome.test.js (fragment)
import { shallowMount, mount } from "@vue/test-utils";

You would notice we're using jest.spyOn() to mock the implementation of console.log to allow us to assert .toHaveBeCalledWith and then restore the console.log to its initial application once our test has completed.

Here is a full example of the test file.

// src/stories/Welcome.test.js
import { shallowMount, mount } from "@vue/test-utils";
import Component from './Welcome.vue';

describe('Welcome', () => {
  let vm
  let wrapper
  beforeEach(() => {
    wrapper = shallowMount(Component)
    vm = wrapper.vm
  })

  describe("props.showApp", () => {
    it('logs message', () => {
      wrapper = mount(Component)
      vm = wrapper.vm
      let prop = vm.$options.props.showApp;

      let spy = jest.spyOn(console, 'log').mockImplementation()
      prop.default()
      expect(console.log).toHaveBeenCalledWith('Welcome to storybook!')
      spy.mockRestore()
    })
  })

  describe('onClick', () => {
    it('calls showApp', () => {
      let showApp = jest.fn()
      wrapper.setProps({
        showApp
      })
      let event = {
        preventDefault: jest.fn()
      }
      vm.onClick(event)
      expect(showApp).toHaveBeenCalled()
      expect(event.preventDefault).toHaveBeenCalled()
    })
  })
})

We can rerun our tests and fingers crossed the coverage should be vastly improved. 🤞

npm test

> [email protected] test ~/code/design-system
> jest

 PASS  src/stories/MyButton.test.js
 PASS  src/stories/Welcome.test.js

=============================== Coverage summary ===============================
Statements   : 100% ( 4/4 )
Branches     : 100% ( 0/0 )
Functions    : 100% ( 3/3 )
Lines        : 100% ( 4/4 )
================================================================================

Test Suites: 2 passed, 2 total
Tests:       3 passed, 3 total
Snapshots:   0 total
Time:        2.404s
Ran all test suites.

That is Awesome, well done!

Notes

With most code challenges, I usually battle through small problems along the way. Here I like to give credit to where I have found solutions to the issues I have experienced while getting the project setup.

Using Jest with Babel as documented required adding [email protected] to the development dependencies to ensure it works well with Babel 7.

You'll notice in the jest.config.js I included a transformIgnorePatterns definition. Although the current code doesn't demand too much from Core.js, I added this definition. It will save some headake later on in your development, avoiding the no descriptive SyntaxError: Unexpected identifier issues.

Thank you for reading, I hope this helped you get your Vue.js Storybook project to the next level.

Vue.js Testing Guide: Unit testing in Vue and Mocha

Vue.js Testing Guide: Unit testing in Vue and Mocha

Testing in Vue is really simple! In this post, you'll learn a basic unit test using Mocha and Vue.js

This document aims to outline some short examples of how to unit test using a Vue project. This guide is specifically designed to be used on the following Vue set up.

What project setup? What packages are needed?

With the above setup, you can now start writing Mocha unit tests in Vue. The steps below show how to create tests.

Creating test files

Files that are picked for the testing end with **_.spec.js_**. You can see how this is set or change this inside **_package.json_**.

package.json

"scripts": {
    "test:unit": "vue-cli-service test:unit src/**/*.spec.js"
  }
How to run tests?

In the terminal type:

npm run test:unit

Let's see how a basic unit test file will look…

import Spinner from "@/ui/Spinner";
import AppLoadingScreen from "./AppLoadingScreen";
import { shallowMount } from "@vue/test-utils";
import { expect } from "chai";

describe("AppLoadingScreen", () => {
  let component;

  beforeEach(() => {
    component = shallowMount(AppLoadingScreen);
  });

  it("should render Spinner on mount", () => {
    expect(component.find(Spinner).exists()).to.be.true;
  });
});

The numbers below reference the code AppLoadingScreen.spec.js code block above.

  1. A component spinner is imported
  2. AppLoadingScreen.vue (the Vue component we are testing is imported)
  3. **_shallowMount_**is imported from Vue utils. It creates a [**_Wrapper_**](https://vue-test-utils.vuejs.org/api/wrapper/)that contains the mounted and rendered Vue component, but with stubbed child components. A stubbed child component is a replacement for a child component rendered by the component under test. Some more details here on how stubbed components work: https://stackoverflow.com/questions/52962489/what-are-stubbed-child-components-in-vue-test-utils
  4. **_expect_** is imported from chai. expect is a chainable language to construct assertions. Assertions are used to test a specific thing in the code. Some examples of what can be chained to expect to test https://www.chaijs.com/api/bdd/

6. **_describe_**is used to outline what you are testing. This will also show in the terminal after running the test. In this case, we are testing the appLoadingScreen component.

9. **_beforeEach()_** is a Mocha method which executes the callback argument before each of the tests. We run **_shallowMount()_** inside **_beforeEach()_** so a component is mounted before every test.

10. **_shallowMount()_** method is used placing our test component inside.

13. **_it_** is where you perform individual tests. You should be able to describe the tests, in our case “it should render Spinner on mount”. This clearly outlines what the test will be.

14. **_expect_** from chai. Asserts that something should be tested. In our case we look inside our component for the Spinner component, by using **_find_** from vue utils. Find returns a wrapper of the first DOM node or Vue component matching selector. As we are using chai expect, we can chain keywords. In this case, we add **_to.be.true_**

Running the above test **_npm run test:unit_**

Displays the results of the test describe being the “AppLoadingScreen” and it being “should render Spinner on mount”.

How we would go about testing when using Vuex.

A **_.spec.js_** file that uses vuex

import Vuex from "vuex";
import { createLocalVue, shallowMount } from "@vue/test-utils";
import chai, { expect } from "chai";
import sinon from "sinon";
import sinonChai from "sinon-chai";
import Modal from "@/ui/Modal";
import BaseButton from "@/ui/BaseButton";

chai.use(sinonChai);

const localVue = createLocalVue();
localVue.use(Vuex);
localVue.component("BaseButton", BaseButton);

describe("Modal", () => {
  let store;
  const getters = {
    isModalOpen: () => true,
    activeModalName: () => "baz"
  };
  const actions = {
    TOGGLE_MODAL: () => true
  };
  let component;
  const mockMethod = sinon.spy();

  beforeEach(() => {
    store = new Vuex.Store({
      getters,
      actions
    });

    component = shallowMount(Modal, {
      store,
      localVue,
    });
  });

  describe("can close when", () => {
    it("clicking 'x'", () => {
      component.setMethods({ TOGGLE_MODAL: mockMethod });
      component.find(".modal-close-btn").trigger("click");
      expect(mockMethod).to.have.been.called.calledWith({
        isOpen: false,
        name: null
      });
    });
  });
});

The numbers below reference the code Modal.spec.js code block above.

  1. We import vuexas we are going to create a store inside the test file.
  2. createLocalVue from vue utils. Is used to create a local class of vue so we can use components, plugins and mixins without polluting the global vue class.

4. sinon is imported. sinon allows you to create test doubles. For example mock methods from the component we want to pull into the test.

5. sinon-chai is imported. Extends Chai with assertions for the Sinon.JS mocking framework.

6. The modal component is imported (the component we are testing)

7. BaseButton is imported — we have to import this component as it is registered globally.

8. chai.use is called and set to use sinonChai

11. createLocalVue is stored in a variable for reuse.

12. Set the localVueto use vuex.

13. Register the BaseButton component as it was previously registered globally.

16. Create the store variable.

17. Create the getters, matching the component getters. ( here we can change the values to help us test, for example, if something is hidden in v-if we can change to true in the test so it becomes available)

18. Create the actions, matching the component.

25. store a mockMethod using sinon.spy(). A spy call is an object representation of an individual call to a spied function, which could be a fake, spy, stub or mock method.

28. A new store is created passing the getters and actions.

33. We create the shallow mount version of the component with the localVue and store passed in.

41. setMethods allows you to use methods from the store, in this case, we are using the action we created, matching the component file action TOGGLE_MODAL. https://vue-test-utils.vuejs.org/api/wrapper-array/#setmethods

42. find is used to search for a selector in the component. From vue Utils https://vue-test-utils.vuejs.org/api/wrapper/#find

42. trigger is used to pass in the event, in this case ‘click’ is the event that would fire on this selector. https://vue-test-utils.vuejs.org/api/wrapper/trigger.html

43. Here is the test. We use expect to assert that the mockmethod to.have.been.called.calledWith chainable methods come from chai and can be used to test a variety of scenarios. calledWith can check the see what arguments have been passed in, this way we can be sure that what gets passed in does not change otherwise the test will fail and indicate why.

Summary

That’s a basic unit test using mocha and Vue.

As you can imagine each component may have different logic in with new test syntax needed. Vuex is commonly used in Vue apps, which adds another layer of complexity when testing.

With this test, we can check that a store action was called, by testing what would happen if we triggered a click event on a selector.

The store logic itself can be tested independently on its own, which would give you a detailed test of what each action and mutation are doing.

Testing Vue with Jest

Testing Vue with Jest

In this article to show how to setup Jest in an Vue.js application. This will guide you through everything in a blank Vue.js template to test components and more

In this article to show how to setup Jest in an Vue.js application. This will guide you through everything in a blank Vue.js template to test components and more

As we’re inside of the Vue.js environment, we’ll also be using <a href="https://alligator.io/vuejs/vue-test-utils-changes/" target="_blank">vue-test-utils</a> to make it easy when interfacing with native Vue elements.

Project Setup

Setting up our testing environment is easy. In previous versions of the Vue.js CLI, we had to do this manually, but now it comes as standard with the project generation.

Ensure you have the Vue.js CLI installed on your machine by doing the following:

$ npm install -g @vue/cli
# OR
$ yarn global add @vue/cli

# Ensure you have the appropriate version (3.x.x>) with
$ vue --version

Create a new project with the CLI with the following:

$ vue create testing-vue

> Manually select features
> Babel, Linter / Preformatter, Unit Testing
> ESLint (your preference)
> Lint on save
> Jest
> In dedicated config files

$ cd testing-vue
$ code .
$ npm run serve 

Testing

Now that we’ve generated our Vue project with Jest, we can navigate to the tests/unit folder. Inside of this folder, we have a file named 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);
  });
});

As referenced inside of our package.json, we can run this unit test by typing:

$ npm run test:unit

This gives us the results of all of the unit tests within our project. At the moment, everything passes as expected.

We can add the --watch flag to this to keep this running in the background as we create and edit new tests.

"scripts": {
  "test:unit": "vue-cli-service test:unit --watch"
}

Unit Testing

In our small example, we’ll create a new component named FancyHeading. This will represent a heading that can be customized with a title and color using props.

<template>
  <h1 :style="headingStyles">{{title}}</h1>
</template>

<script>
export default {
  data() {
    return {
      headingStyles: {
        color: this.color
      }
    };
  },
  props: ["title", "color"]
};
</script>

In order to unit test this, we’ll need to make a corresponding FancyHeading.spec.js file within the tests/unit directory.

A test suite can be thought of as a collection of tests centered around testing a particular module or functionality.

Let’s take a look at our first unit test with Jest and Vue. We’ll investigate it line by line:

import Vue from 'vue';
import FancyHeading from '@/components/FancyHeading.vue';

function mountComponentWithProps (Component, propsData) {
  const Constructor = Vue.extend(Component);
  const vm = new Constructor({
    propsData
  }).$mount();

  return vm.$el;
}

describe('FancyHeading.vue', () => {
  it('should be the correct color', () => {
    const headingData = mountComponentWithProps(FancyHeading, { color: 'red' });
    const styleData = headingData.style.getPropertyValue('color');

    console.log(styleData)

    expect(styleData).toEqual('blue');
  });

  it('should have the correct title', () => {
    const headingData = mountComponentWithProps(FancyHeading, { title: 'Hello, Vue!' });
    const titleData = headingData.textContent;

    expect(titleData).toEqual('Hello, Vue!');
  });
});

  1. We start off by importing Vue and the necessary components that we want to test.
  2. We use describe to encapsulate numerous unit tests surrounding our FancyHeading component.
  3. Each unit test is created with the it function, firstly providing a description of exactly what we’re testing, followed by a function.
  4. In our first assertion, It must have the correct color, we’re mounting our component to a new Vue instance with mountComponentWithProps.
  5. We’re then able to create a variable, styleData which contains what we expect to receive from our test.
  6. Finally, we assert that this is true by using expect. If we check our terminal with $ npm run test:unit --watch, we’ll see a PASS for this unit test.

We take a similar approach when testing the title of our heading in the second unit test.

Top Vue.js Developers in USA

Top Vue.js Developers in USA

Vue.js is an extensively popular JavaScript framework with which you can create powerful as well as interactive interfaces. Vue.js is the best framework when it comes to building a single web and mobile apps.

We, at HireFullStackDeveloperIndia, implement the right strategic approach to offer a wide variety through customized Vue.js development services to suit your requirements at most competitive prices.

Vue.js is an open-source JavaScript framework that is incredibly progressive and adoptive and majorly used to build a breathtaking user interface. Vue.js is efficient to create advanced web page applications.

Vue.js gets its strength from the flexible JavaScript library to build an enthralling user interface. As the core of Vue.js is concentrated which provides a variety of interactive components for the web and gives real-time implementation. It gives freedom to developers by giving fluidity and eases the integration process with existing projects and other libraries that enables to structure of a highly customizable application.

Vue.js is a scalable framework with a robust in-build stack that can extend itself to operate apps of any proportion. Moreover, vue.js is the best framework to seamlessly create astonishing single-page applications.

Our Vue.js developers have gained tremendous expertise by delivering services to clients worldwide over multiple industries in the area of front-end development. Our adept developers are experts in Vue development and can provide the best value-added user interfaces and web apps.

We assure our clients to have a prime user interface that reaches end-users and target the audience with the exceptional user experience across a variety of devices and platforms. Our expert team of developers serves your business to move ahead on the path of success, where your enterprise can have an advantage over others.

Here are some key benefits that you can avail when you decide to hire vue.js developers in USA from HireFullStackDeveloperIndia:

  • A team of Vue.js developers of your choice
  • 100% guaranteed client satisfaction
  • Integrity and Transparency
  • Free no-obligation quote
  • Portal development solutions
  • Interactive Dashboards over a wide array of devices
  • Vue.js music and video streaming apps
  • Flexible engagement model
  • A free project manager with your team
  • 24*7 communication with your preferred means

If you are looking to hire React Native developers in USA, then choosing HireFullStackDeveloperIndia would be the best as we offer some of the best talents when it comes to Vue.js.