Back to Notes

How To

Unit Testing Vue 3 Lifecycle Hooks in Composables with Vitest

April 3, 2024

0min read

Learn how to unit test lifecycle hooks in composables using Vitest and the powerful withSetup utility function.

As a front-end developer working with Vue 3, I often find myself using composables to encapsulate and reuse logic across multiple components. However, testing the lifecycle hooks within these composables can be a bit tricky. That's where the handy withSetup utility function comes into play.

The withSetup function allows us to run the setup method of a composable, giving us access to its reactive state and lifecycle hooks. This makes it possible to write comprehensive unit tests that ensure our composables are functioning as expected.

Here's how the withSetup function works:

import { createApp } from 'vue'

export function withSetup(hook) {
  let result
  const app = createApp({
    setup() {
      result = hook()
      return () => {}
    }
  })
  app.mount(document.createElement('div'))
  return [result, app]
}

This function takes a hook parameter, which is the composable function we want to test. It creates a new Vue application instance and runs the composable's setup method within that instance. The result of the setup method is stored in the result variable, which we can then assert against in our tests.

The function also returns an array with two elements: the result object and the app instance. We need to unmount the app instance after each test to avoid memory leaks.

Now, let's look at an example of how we can use withSetup to test a composable that uses lifecycle hooks:

import { ref } from 'vue'
import { expect, vi, test } from 'vitest'
import { flushPromises } from '@vue/test-utils'
import { useShowDetails } from '@/composables/showDetails'
import { withSetup } from '../../components/__tests__/utils/withSetup'

// Mocking dependencies...

test('showDetails with popup & no route params', async () => {
  const [result, app] = withSetup(useShowDetails)
  await flushPromises()

  expect(result.isLoading.value).toBe(false)
  expect(result.showDetails.value).toMatchInlineSnapshot({ ... })

  app.unmount()
})

In this example, we're testing the useShowDetails composable, which likely has some lifecycle hooks that need to be tested. We import the withSetup function and pass our composable function to it, destructuring the result and app instance from the returned array.

After calling withSetup, we use Vue Test Utils' flushPromises to wait for any pending promises to resolve (e.g., async lifecycle hooks). Then, we can assert against the composable's reactive state, which includes any data that may have been modified by the lifecycle hooks.

Finally, we call app.unmount() to tear down the Vue instance and avoid memory leaks.

By using the withSetup utility function, we can easily test our composables and ensure that their lifecycle hooks are working as intended. This approach promotes better code quality and maintainability, giving us confidence that our Vue 3 applications will function correctly in various scenarios.

As a front-end developer working with Vue 3, I often find myself using composables to encapsulate and reuse logic across multiple components. However, testing the lifecycle hooks within these composables can be a bit tricky. That's where the handy withSetup utility function comes into play.

The withSetup function allows us to run the setup method of a composable, giving us access to its reactive state and lifecycle hooks. This makes it possible to write comprehensive unit tests that ensure our composables are functioning as expected.

Here's how the withSetup function works:

import { createApp } from 'vue'

export function withSetup(hook) {
  let result
  const app = createApp({
    setup() {
      result = hook()
      return () => {}
    }
  })
  app.mount(document.createElement('div'))
  return [result, app]
}

This function takes a hook parameter, which is the composable function we want to test. It creates a new Vue application instance and runs the composable's setup method within that instance. The result of the setup method is stored in the result variable, which we can then assert against in our tests.

The function also returns an array with two elements: the result object and the app instance. We need to unmount the app instance after each test to avoid memory leaks.

Now, let's look at an example of how we can use withSetup to test a composable that uses lifecycle hooks:

import { ref } from 'vue'
import { expect, vi, test } from 'vitest'
import { flushPromises } from '@vue/test-utils'
import { useShowDetails } from '@/composables/showDetails'
import { withSetup } from '../../components/__tests__/utils/withSetup'

// Mocking dependencies...

test('showDetails with popup & no route params', async () => {
  const [result, app] = withSetup(useShowDetails)
  await flushPromises()

  expect(result.isLoading.value).toBe(false)
  expect(result.showDetails.value).toMatchInlineSnapshot({ ... })

  app.unmount()
})

In this example, we're testing the useShowDetails composable, which likely has some lifecycle hooks that need to be tested. We import the withSetup function and pass our composable function to it, destructuring the result and app instance from the returned array.

After calling withSetup, we use Vue Test Utils' flushPromises to wait for any pending promises to resolve (e.g., async lifecycle hooks). Then, we can assert against the composable's reactive state, which includes any data that may have been modified by the lifecycle hooks.

Finally, we call app.unmount() to tear down the Vue instance and avoid memory leaks.

By using the withSetup utility function, we can easily test our composables and ensure that their lifecycle hooks are working as intended. This approach promotes better code quality and maintainability, giving us confidence that our Vue 3 applications will function correctly in various scenarios.

As a front-end developer working with Vue 3, I often find myself using composables to encapsulate and reuse logic across multiple components. However, testing the lifecycle hooks within these composables can be a bit tricky. That's where the handy withSetup utility function comes into play.

The withSetup function allows us to run the setup method of a composable, giving us access to its reactive state and lifecycle hooks. This makes it possible to write comprehensive unit tests that ensure our composables are functioning as expected.

Here's how the withSetup function works:

import { createApp } from 'vue'

export function withSetup(hook) {
  let result
  const app = createApp({
    setup() {
      result = hook()
      return () => {}
    }
  })
  app.mount(document.createElement('div'))
  return [result, app]
}

This function takes a hook parameter, which is the composable function we want to test. It creates a new Vue application instance and runs the composable's setup method within that instance. The result of the setup method is stored in the result variable, which we can then assert against in our tests.

The function also returns an array with two elements: the result object and the app instance. We need to unmount the app instance after each test to avoid memory leaks.

Now, let's look at an example of how we can use withSetup to test a composable that uses lifecycle hooks:

import { ref } from 'vue'
import { expect, vi, test } from 'vitest'
import { flushPromises } from '@vue/test-utils'
import { useShowDetails } from '@/composables/showDetails'
import { withSetup } from '../../components/__tests__/utils/withSetup'

// Mocking dependencies...

test('showDetails with popup & no route params', async () => {
  const [result, app] = withSetup(useShowDetails)
  await flushPromises()

  expect(result.isLoading.value).toBe(false)
  expect(result.showDetails.value).toMatchInlineSnapshot({ ... })

  app.unmount()
})

In this example, we're testing the useShowDetails composable, which likely has some lifecycle hooks that need to be tested. We import the withSetup function and pass our composable function to it, destructuring the result and app instance from the returned array.

After calling withSetup, we use Vue Test Utils' flushPromises to wait for any pending promises to resolve (e.g., async lifecycle hooks). Then, we can assert against the composable's reactive state, which includes any data that may have been modified by the lifecycle hooks.

Finally, we call app.unmount() to tear down the Vue instance and avoid memory leaks.

By using the withSetup utility function, we can easily test our composables and ensure that their lifecycle hooks are working as intended. This approach promotes better code quality and maintainability, giving us confidence that our Vue 3 applications will function correctly in various scenarios.

As a front-end developer working with Vue 3, I often find myself using composables to encapsulate and reuse logic across multiple components. However, testing the lifecycle hooks within these composables can be a bit tricky. That's where the handy withSetup utility function comes into play.

The withSetup function allows us to run the setup method of a composable, giving us access to its reactive state and lifecycle hooks. This makes it possible to write comprehensive unit tests that ensure our composables are functioning as expected.

Here's how the withSetup function works:

import { createApp } from 'vue'

export function withSetup(hook) {
  let result
  const app = createApp({
    setup() {
      result = hook()
      return () => {}
    }
  })
  app.mount(document.createElement('div'))
  return [result, app]
}

This function takes a hook parameter, which is the composable function we want to test. It creates a new Vue application instance and runs the composable's setup method within that instance. The result of the setup method is stored in the result variable, which we can then assert against in our tests.

The function also returns an array with two elements: the result object and the app instance. We need to unmount the app instance after each test to avoid memory leaks.

Now, let's look at an example of how we can use withSetup to test a composable that uses lifecycle hooks:

import { ref } from 'vue'
import { expect, vi, test } from 'vitest'
import { flushPromises } from '@vue/test-utils'
import { useShowDetails } from '@/composables/showDetails'
import { withSetup } from '../../components/__tests__/utils/withSetup'

// Mocking dependencies...

test('showDetails with popup & no route params', async () => {
  const [result, app] = withSetup(useShowDetails)
  await flushPromises()

  expect(result.isLoading.value).toBe(false)
  expect(result.showDetails.value).toMatchInlineSnapshot({ ... })

  app.unmount()
})

In this example, we're testing the useShowDetails composable, which likely has some lifecycle hooks that need to be tested. We import the withSetup function and pass our composable function to it, destructuring the result and app instance from the returned array.

After calling withSetup, we use Vue Test Utils' flushPromises to wait for any pending promises to resolve (e.g., async lifecycle hooks). Then, we can assert against the composable's reactive state, which includes any data that may have been modified by the lifecycle hooks.

Finally, we call app.unmount() to tear down the Vue instance and avoid memory leaks.

By using the withSetup utility function, we can easily test our composables and ensure that their lifecycle hooks are working as intended. This approach promotes better code quality and maintainability, giving us confidence that our Vue 3 applications will function correctly in various scenarios.

Get in touch

Seeking a fresh opportunity or have an inquiry? Don't hesitate to reach out to me.

Get in touch

Seeking a fresh opportunity or have an inquiry? Don't hesitate to reach out to me.

Get in touch

Seeking a fresh opportunity or have an inquiry? Don't hesitate to reach out to me.

©

2024

Dylan Britz