This is seriously great but it might be combined with another library called “Vue-testing-library” which enforces good test practices and a more BDD-ish approach (Behavior Driven Development).

A kind of BDD can be achieved in Unit Tests as well and, it’s a good practice separating TDD test cases from BDD ones. BDD focuses more on the actual behavior than “input/output” related to a function/component and I would like to demonstrate how “vue-testing-library” can be handy in these situations.

For the records, Vue-testing-library is based on top of Vue/Test-Utils so you can achieve similar things with Vue/Test-Utils only, but it requires more self-discipline.

It’s possible to install it with a single command:

npm install --save-dev vue-testing-library

In this example, I am using Jest-Dom which is really helpful combined to Jest (it helps with DOM assertions and expectations).

A simple challenge is the popular “Counter”, where an end-user could increase or decrease a number through a couple of buttons on the page.

First of all, let’s focus on this through the end user's eye and gather the requirements:

A text showing the actual number (we might expect 0 at the beginning);

A button saying “increase” which should increase the number;

A button saying “decrease” which should decrease the number;

Let’s write our first test case “A text showing the actual number (we might expect 0 at the beginning)”:

//counter.spec.js import { render } from 'vue-testing-library';

import Counter from '@/components/Counter.vue'



describe('Component: Counter.vue', () => {

describe('Behavior', () => {

test('User should see 0 at the beginning', () => {

const { getByText } = render(Counter);

const textNumberWrapper = getByText(/Counter is:/);

expect(textNumberWrapper).toHaveTextContent('Counter is: 0');

});

});

});

The imported Render function will just take care of mounting the desired component in isolation and it returns a set of utilities specific to the wrapped instance. More information can be found at the following link:

The other two instructions in the test case are actually simple: the first one selects the element containing the desired text and the final one just checks whether the previously found element’s contained text is what we expect (“Counter should be 0 at the beginning”).

A simple way to turn this test into a successful one is the following Vue Component implementation:

// src/components/Counter.vue <template>

<div>

<div class="counter-text">

Counter is: 0

</div>

</div>

</template>

You might notice that our test doesn’t search for a specific tag. That’s a benefit because it helps to rely on Test First Development and leave a margin of flexibility. In addition, end users are not focusing on the nature of HTML tags or hierarchy but only on the displayed content.

So, now you could imagine how the Increase button’s test case, might look like the following:

// counter.spec.js import {

render,

fireEvent

} from 'vue-testing-library';

import Counter, { INITIAL_COUNTER } from '@/components/Counter.vue'



describe('Component: Counter.vue', () => {

describe('Behavior', () => {

...

test('When User clicks on Increase, Counter should be increased by 1 unit', async () => {

const { getByText } = render(Counter);

const textNumberWrapper = getByText(/Counter is:/);

expect(textNumberWrapper).toHaveTextContent(`Counter is: ${INITIAL_COUNTER}`);

await fireEvent.click(getByText('Increase'));

expect(textNumberWrapper).toHaveTextContent(`Counter is: ${INITIAL_COUNTER + 1}`);

});

});

});

First of all, you could see that this test case is running asynchronously since the fireEvent function is returning a promise and we are waiting for the execution with the await keyword.

This test case is pretty straight forward:

It’s rendering the component in an isolated state;

It checks whether our counter tracker is showing the initial state;

It triggers a click event on any element with the “Increase” text and waits for the execution;

Finally, it checks whether our counter trackers got updated with a new number (in this case we expect 1 unit more).

In order to resolve this test case, let’s implement the component:

// src/components/Counter.vue <template>

<div>

<div class="counter-text">

Counter is: {{ count }}

</div>

<div class="controls">

<button @click="increase">Increase</button>

</div>

</div>

</template>

<script lang="js">

export const INITIAL_COUNTER = 0;



export default {

name: 'Counter',

data () {

return {

count: INITIAL_COUNTER

};

},

methods: {

increase() {

this.count += 1;

}

}

}

</script>

Cool!!

What about the Decrease functionality?

// counter.spec.js ... describe('Component: Counter.vue', () => {

describe('Behavior', () => {

...

test('When User clicks on Decrease, Counter should be decreased by 1 unit', async () => {

const { getByText } = render(Counter);

const textNumberWrapper = getByText(/Counter is:/);

expect(textNumberWrapper).toHaveTextContent(`Counter is: ${INITIAL_COUNTER}`);

await fireEvent.click(getByText('Decrease'));

expect(textNumberWrapper).toHaveTextContent(`Counter is: ${INITIAL_COUNTER - 1}`);

});

});

});

And the implementation is similar to the other one:

// src/components/Counter.vue <template>

<div>

...

<div class="controls">

...

<button @click="decrease">Decrease</button>

</div>

</div>

</template>

<script lang="js">

export const INITIAL_COUNTER = 0;



export default {

name: 'Counter',

data () {

return {

count: INITIAL_COUNTER

};

},

// define methods under the `methods` object

methods: {

increase() {

this.count += 1;

},

decrease() {

this.count -= 1;

}

}

}

</script>

The complete example is the following:

And Jest output is the following:

PASS tests/unit/counter.spec.js Component: Counter.vue Behavior ✓ User should see 0 at the beginning ✓ When User clicks on Increase, Counter should be increased by 1 unit ✓ When User clicks on Decrease, Counter should be decreased by 1 unit

Now the interesting part.

What happens if you change tags or hierarchy (for instance: change classes, span instead of div, or different nested HTML tags)? Unless we change the text, nothing really impacts our tests. Brilliant!

In my humble opinion, I seriously believe this is a great way of covering User’s behavior in our Unit Tests since these tests are running pretty fast and in isolation (and they are flexible to implementation changes too).

You can find more examples at the following link:

Cheers :)