Testing out a login form in Vue framework

Hi there! I recently put together a login form using the Vue.js framework, and now I'm looking to write some tests for my API calls. Although I'm still new to Vue.js, I'm eager to learn more about testing in this environment.

Here's the code snippet for my form:

<template>
  <div>
    <div class="col q-mb-md">
      <div class="row">
        <q-btn
          @click="loginGoogle"
          type="submit"
          color="primary"
          class="col"
          name="login_google"
          outline
          size="lg"
          label="Login with Google" />
      </div>
    </div>
    <div class="col q-mb-md">
      <div class="row">
      <q-btn
        type="submit"
        color="primary"
        class="col"
        name="login_apple"
        outline
        size="lg"
        label="Login with Apple ID" />
      </div>
    </div>
    <form @submit.prevent="submitForm" class="q-mt-lg">
        <div class="col q-mb-md">
          <q-input
            v-model="formData.email"
            outlined
            class="col"
            label="Email"
            ref="email"
            stack-label
            :rules="[ val => validateEmail(val) || 'Please enter a valid email address']"
            lazy-rules />
        </div>
        <div class="col q-mb-md">
          <q-input
            v-model="formData.password"
            type="password"
            outlined
            class="col"
            label="Password"
            ref="password"
            stack-label
            :rules="[ val => val.length >= 8 || 'Password must be at least 8 characters']"
            lazy-rules />
        </div>
        <div class="row">
            <q-btn
            type="submit"
            name="login"
            class="login"
            color="primary"
            label="Login" />
        </div>
    </form>
  </div>
</template>

Let me show you the action method that triggers the API call:

submitForm () {
  this.$refs.email.validate()
  this.$refs.password.validate()
  if (!this.$refs.email.hasError && !this.$refs.password.hasError) {
    authService.login({ email: this.formData.email.toLowerCase(), password: this.formData.password })
      .then((res) => {
        this.$store.commit('SET_AUTH_USER')
        localStorage.setItem('access_token', res.access_token)
        localStorage.setItem('refresh_token', res.refresh_token)
        this.$store.dispatch('GET_ME').then((me) => {
          this.$router.push({ path: '/' })
        })
      }).catch((err) => {
        if (err.status === 500) {
          this.$swal({ icon: 'error', title: 'Something went wrong!' })
        } else {
          this.$swal({ icon: 'error', title: 'Wrong data!' })
        }
      })
  }
}

This is how I structured my API call:

/**
 * Send request for login user and set token in localstorage
 *
 * @param {String} email
 * @param {String} password
 * @returns {Object}
 */
async function login (data) {
  const requestOptions = {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
      Accept: 'application/json'
    },
    body: `email=${data.email.trim()}&password=${data.password.trim()}`
  }
  const res = await fetch('http://localhost/api/login', requestOptions)
  if ((await res.status) !== 200) {
    // eslint-disable-next-line no-throw-literal
    throw { status: res.status, message: res.statusText }
  } else {
    return await res.json()
  }
}

I attempted to test my form by mocking the action from vuex store as advised. However, the test didn't produce the expected results. Here's what happened:

import Vuex from 'vuex'
import { mount, createLocalVue } from '@vue/test-utils'
import LoginComponent from '../components/Auth/Login'

const localVue = createLocalVue()
localVue.use(Vuex)

describe('Login form', () => {
  it('calls the login action correctly', () => {
    const loginMock = jest.fn(() => Promise.resolve())
    const store = new Vuex.Store({
      actions: {
        // mock function
        submitForm: loginMock
      }
    })
    const wrapper = mount(LoginComponent, { localVue, store })
    wrapper.find('form').trigger('submit.prevent')
    expect(loginMock).toHaveBeenCalled()
  })
})

The test failed, indicating that the login action was not called as expected:

  Login form
    ✕ calls the login action correctly (50 ms)

  ● Login form › calls the login action correctly

    expect(jest.fn()).toHaveBeenCalled()

    Expected number of calls: >= 1
    Received number of calls:    0

      18 |     const wrapper = mount(LoginComponent, { localVue, store })
      19 |     wrapper.find('form').trigger('submit.prevent')
    > 20 |     expect(loginMock).toHaveBeenCalled()
         |                       ^
      21 |   })
      22 | })
      23 |

      at Object.<anonymous> (src/specs/login.spec.js:20:23)

If you have any suggestions on other tests I should include to ensure the functionality of my form, please let me know! Your insights are much appreciated. Thank you!

Answer №1

One must wait for the trigger as it initiates an asynchronous function call

await wrapper.find('form').trigger('submit.prevent')

followed by

wrapper.$nextTick(() => {
   expect(loginMock).toHaveBeenCalled()
})

Answer №2

Revise the final part of the code, you forgot to include vm.

wrapper.vm.$nextTick(() => { expect(loginMock).toHaveBeenCalled(); });

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Replace the default focus state using CSS or resetting it to a custom style

I'm looking for a solution similar to a CSS reset, but specifically for the :focus state. If such a thing doesn't exist yet, I'm interested in learning about the possible properties that can be reset or overridden in order to create a new :f ...

Dealing with browser timeouts for HTTP requests using JavaScript

Managing time out situations in a web application when calling a REST API is crucial. Below is the code snippet I am using to make the API call with jQuery ajax. $.ajax({ type: "POST", url: endpoint, data: payload, ...

Error encountered with structured array of objects in React Typescript

What is the reason for typescript warning me about this specific line of code? <TimeSlots hours={[{ dayIndex: 1, day: 'monday', }]}/> Can you please explain how I can define a type in JSX? ...

What could be causing the Gruntfile to throw an error?

Getting an unexpected error while trying to run grunt $ grunt Loading "Gruntfile.js" tasks...ERROR >> SyntaxError: Unexpected token : Warning: Task "default" not found. Use --force to continue. Execution terminated due to warnings. Here is my ...

Acquire a portion of a string with JavaScript or jQuery

Given the string testserver\sho007, how can I utilize jQuery to extract only the value sho007? ...

Sending the method's URL in the controller through an AJAX call

Below is the code snippet for an ajax call: <script> jQuery(document).ready(function() { $("#VEGAS").submit(function(){ var form_data = $("#VEGAS").serialize(); var routeUrl = "<?= url('/'); ?> /PUBLIC/vpage"; $.ajax({ ...

Alter the truth value of an item contained within an array

Embarking on my JavaScript journey, so please bear with me as I'm just getting started :) I am working on a small app where the images on the left side are stored in an array. When a user clicks on one of them, I want to change its height and also tog ...

Looking to automate clicking in Vue.js?

Upon pressing the search button, I need to display the products in the background and choose one. Here are the codes: //Specify the area to click each time <div v-for="(item, index) in filteredProducts" @click="addToList(item)">& ...

Retrieve data from Last.fm API by utilizing both Node.js and Angular framework

I am currently working on implementing the node-lastfmapi track.search method into my project. I have successfully retrieved the results, but I am facing challenges in integrating them into the front end using Angular. My backend is powered by mongoDB and ...

Ensure you wait for the next tick before making any assertions using vitest

Within a composable, I have implemented a reactive primitive foo and a function onFooChange. There is a watcher in this composable that monitors changes to foo and calls onFooChange upon any change. I am looking to write a unit test to verify the executio ...

Serialization not being successful

Having an issue with my form that is being loaded and posted using ajax. When trying to send the data, nothing is added to the post. Here's a simplified version of the code: <form id="userForm"> <input type="text" name="username" /> ...

Guide to embed a Google Sheets chart into a WordPress website

I'm looking to include a chart in a wordpress post, using the script generated from google sheets' publish function. When I add the script to a generic HTML page, the chart displays properly. However, when I insert it into a wordpress post, I en ...

I am consistently running into an Uncaught Syntax error within my Express server.js while using Angular 1.5.6

I've been struggling for hours to integrate Angular with routes that I've created. Eventually, I decided to give Express a try and set up a basic server.js file to run as a standalone server. However, nothing seems to be working and I keep encou ...

Receiving an undefined value of x in AJAX and web API

In the Javascript code, I am attempting to display the listview of reviews when a user clicks on a movie. However, I am encountering errors in the console for the second and third movies - Batman and Avatar - stating that they are not defined. Additionally ...

Is there a way to adjust the timepicker value directly using the keyboard input?

Is there a way to control the material-ui-time-picker input using the keyboard instead of clicking on the clock? This is my current code: import React, { Component } from "react"; import { TimePicker } from "material-ui-time-picker"; import { Input as Ti ...

Exploring the possibilities of custom layouts for specific routes within the pages directory in Next.js

I am interested in incorporating layout-based routing within my project's pages directory. I want to find a way to have a specific file, like the _app.tsx, that can only affect the files located inside a particular folder. This setup would operate si ...

Importance of value attribute in <input ng-model ..>

Maybe a silly inquiry, but I'm curious about the purpose of having value="" in this particular situation: <input ng-model="something.name" value="" class="input-xlarge" /> Are there any alternatives to keeping the value attribute empty? I init ...

What causes req.body to be null after using event.preventDefault() in conjunction with fetch api, express, and node.js?

Is there a way to submit a form without causing the page to reload and still retrieve an object from MongoDB using server side scripts? I've tried preventing the default behavior of the form with an event handler to avoid the page refresh, but this ca ...

How can I accurately show the server time and timezone in JS/jQuery regardless of the user's location?

Apologies for bringing up another time and timezone question related to web development. Despite numerous resources available, I am still struggling to grasp the concept of converting time between PHP and JavaScript. Here is my scenario: I aim to retrieve ...

What is the best way to wrap `useFetch` in order to leverage reactivity?

When I wrap useFetch() as a composable to customize the baseURL and automatically set an authentication token, I encounter reactivity issues when calling the composable within a component without using the await keyword. Typically, I would call const { dat ...