How to Test CSS Modules with Vitest

Using CSS-Modules in Vue single-file components is simple (compared to other frontend libraries/frameworks). We add the module attribute to the style tag and we’re good to go. However, there may be some scenarios where we might want to use these CSS-Module classes in our unit tests. Let’s see how to test Vitest CSS Module classes.

CSS Classes in Unit Tests

There are two main reasons for using CSS classes in unit tests. The first is to find elements, however, this approach is not a good practice, since these classes can be renamed, or even removed. A better approach is using another attribute, which has one purpose of finding elements. Furthermore, it can also be used for e2e tests. For example:

  message goes here

The second reason is asserting that an element has a certain class. Mostly these classes are dynamically applied depending on a condition, that’s what we will be testing in this tutorial.

ChatBubble Example Component

The example we are going to use is a basic ChatBubble component similar to what we use in messaging apps. It will have a wrapper class applied to the root element, and either a left or right class applied dynamically to change its direction:

<script setup lang="ts">
  import { type PropType } from 'vue'

  type direction = 'left' | 'right'

    direction: {
      type: String as PropType<Side>,
      default: 'left',

    <slot />

<style lang="scss" module>
  $color-gray: #a8b2b3;

  .wrapper {
    position: relative;
    background: $color-gray;
    border-radius: 0.4em;
    padding: 1rem;
    max-width: 10rem;
    color: black;
    font-weight: bold;
    text-align: left;

  .right:after {
    content: '';
    position: absolute;
    top: 50%;
    width: 0;
    height: 0;
    border: 18px solid transparent;
    margin-top: -18px;

  .left:after {
    right: 0;
    border-left-color: $color-gray;
    border-right: 0;
    margin-right: -18px;

  .right:after {
    left: 0;
    border-right-color: $color-gray;
    border-left: 0;
    margin-left: -18px;

After using the component we can end up with something like this:

Vute 3 ChatBubble component with CSS-Modules

I am not sure if this conversation is accurate, as I’ve never purchased a Pizza from Papa John’s.

Test Vitest CSS Modules

To get started, we add configuration to Vite:

/// <reference types="vitest" />

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  test: {
    environment: 'happy-dom',
    css: {
      modules: {
        classNameStrategy: 'non-scoped',
  plugins: [vue()],

Since CSS-Module classes will be hashed to make them scoped to the current component, that produces issues for us when trying to use them in our tests, as we don’t know what the hash will be. classNameStrategy tells Vite not to scope the classes in our test environment.

Next, we create the test file:

import { describe, test, expect } from 'vitest'
import { shallowMount } from '@vue/test-utils'
import ChatBubble from './ChatBubble.vue'

describe('ChatBubble Component', () => {
  test('applies "left" class name when direction prop value is "left"', () => {
    const wrapper = shallowMount(ChatBubble, {
      props: {
        direction: 'left',


  test('applies "right" class name when direction prop value is "right"', () => {
    const wrapper = shallowMount(ChatBubble, {
      props: {
        direction: 'right',


As we can see, we are checking that the right class name is applied depending on the value of the direction prop.

Vitest unit-tests passing

Amenallah Hsoumi
Amenallah Hsoumi

Senior Software Engineer, Indie Hacker, building cool stuff on the web!

Articles: 19