TDD With Vue.js Part 1 — Simple Page

Iqbal Djulfri
The Startup
Published in
8 min readAug 10, 2020

Disclaimer: I assume you have some knowledge in unit testing with Vue.js, and what is TDD in general. I will go straight to the point without telling you what each function do. If you don’t have this knowledge, you can go to https://vue-test-utils.vuejs.org/ to learn more about it.

In this part, I will show you a step-by-step guide to do TDD with Vue.js on a simple login page. A very simple login page consists of text input for username, text input for the password, and a submit button. Here is an example:

Forget what you’ve learned, because TDD will introduce a new mindset, a conflicting one. Please bear with me, because I’ve had a lot of questions when learning TDD too.

The first step in TDD is to create a failing test case. It’s okay for it to fail, as long as it contains what you really need for your component. Create a test file LoginPage.spec.js and the first test case. We will check the required elements upon loaded. It checks whether the elements is there or not and also check some labeling. You don’t need to do any refactoring in this step, since it will be done later.

Tip: use describe() to group your test cases. So you or other people can grasp what your component can do by looking at the test results.

Tip: on most cases, you only need shallowMount() instead of mount(). You want to isolate the test as much as possible to make it easier to test.

If you run npx jest LoginPage this test will fail and shows something likeCannot find module ‘./LoginPage’ from ‘LoginPage.spec.js’

Tip: use npx jest LoginPage --watch to make your life easier. The --watch flag will watch for file changes, and rerun the test automatically.

The next step is to make it green. However, please remember, in TDD you have to do minimum implementation to make it green, don’t add any other code. So, create a LoginPage.vue file with minimum implementation and rerun the test.

It fails because there is no element with #title. So we need to add the title element. See what happens to the test result.

Now add a little code to the implementation to make it green. Remember, don’t refactor at this point.

By this point, I assume you have some ideas of how this works. For the next few steps, I will show the implementation and the test result in a series to make this tutorial simpler.

Now the test has passed (green), we can continue to the next step: refactor. Take a look at your code, both the test and the implementation. Is there anything that appears more than once? Or is there anything to simplify? Do refactor on those code, but do not over-refactor it. Refactor as needed.

Tip: don’t forget to retest after you refactor.

Now we can add a new test case. This time we will check for the submit button behavior. The submit button will hit try to call the login API and redirect you to homepage when clicked. However it will try to validate the username and password first. In this section, we will learn how to mock external library and the router.

First create test case, however this time, some test cases. Most of the time you know what your component will do, so by leveraging it.todo() , you can list your component specification and structure your test files.

Tip: It’s better to organize your test with positive test cases (normal, success flow) first, then the negative cases (failing flow), and edge cases.

Tip: Test the expected behavior, not the method. Ask yourself: what is the outcome of an action in the UI.

Tip: In most cases, these are the entry point of things you should test: UI actions (v-on), emitted values, component life cycles (created, mounted, etc), store state changes, props changes. Avoid test using wrapper.vm.* unless necessary.

Tip: In most cases, these are the behavior you should expect: UI changes (including sub-component attribute changes), library function calls, emitted values, store mutations and actions, route changes.

Let’s take a look at the test file first.

  1. Import API library
    I use Jest’s module name mapper for this shortcut.
  2. Mock the API library
    You can use jest.mock() to the implementation. If you use this, then ALL members of @/lib/api will be doubled (mocked) even in the implementation. This is very useful if you want to mock external libraries.
  3. Mock $router implementation
    As I said earlier, you want to isolate the test, and that is including the router, vuex, store, sub component, other libraries, etc. It’s a different story if you want to do E2E or integration tests.
  4. Mock implemenatation of api.login() function
    Since we mocked the @/lib/api earlier, every member of it becomes a jest.fn() instance (a synchronous function). We also know that API call is handled asynchronously. So, let’s mock it to return a resolved promise.
  5. Wait for promise to be resolved

Now let’s create the implementation. Remember: As minimum as possible.

Now, let’s refactor. The code will look more organized, and we didn’t repeat anything. Be careful not to over-refactor.

Onward to the next test case. We will test for case when login API sends error that makes the login failed. Write the test case when api.login() promise is rejected an error message is shown, but the error message is not shown when this component is loaded.

Next, create the implementation. Remember, only code what you need to make this test green.

What?! We got 2 errors?
No need to panic, this is expected since we add a test case in the another it(). This is a common occurrence in TDD. We just have to make it green.

Wait, the previous error strikes again. Did I do something wrong?
Nope, quite the opposite, you do the right thing to code only what you need to make the test green. Let’s solve this.

Wait, the code seems to be right, but the error persists? What’s wrong?
Take a look at the test code. We use wrapper.vm.$nextTick() to wait for api.login()promise to be rejected. However, we set the this.isError to true , and this action will trigger UI change and UI change is considered a promise. so we need to add 1 more wrapper.vm.$nextTick() to wait for UI change to finish.

Ok, it’s green. We can refactor it then. Instead of calling multiple wrapper.vm.$nextTick() it’s better to install flush-promises library to ease these kinds of promise waiting.

Okay, for the last 2 test case, we can merge them because they similar to each other.

The test result produces errors because we introduce something that wasn’t handled before. We just need to handle this in the test case.

All green, let’s refactor.

So, what’s next? We have learned how to mock $router and external library, do TDD, and refactor as needed. However to make this code production ready we need to do something. Remember the API call? We still have not passed anything to the function. And also, we haven’t painted the template with CSS. It is up to you to continue since adding CSS won’t break any of the tests.

Personally I like to add CSS as the last step because I have to run the server. I hope this tutorial will do you some help. Please let me know if something I can fix or help. Thank you for reading.

Further Reading

This is a very good tutorial for learning the TDD concept and Golang:

All of the code in this tutorial can be accessed in:

--

--