# Unit testing

Learn how to write unit tests for your Nuxt integration.

The Nuxt SDK supports testing with [Vitest](https://vitest.dev) and [Nuxt Test Utils](https://nuxt.com/docs/getting-started/testing).

Here is what you need to check:

- Make sure your tracking calls are triggered correctly
- Verify you have defined fallbacks for your content
- Ensure you are handling evaluation errors

In the following sections, we will go through each of these points in detail.

## Set up Vitest

Install the required dependencies:

```sh
npm install -D vitest @nuxt/test-utils @vue/test-utils happy-dom
```

Create a `vitest.config.ts` in the root of your project:

```ts
import {defineVitestConfig} from '@nuxt/test-utils/config';

export default defineVitestConfig({
  test: {
    globals: true,
    environment: 'nuxt',
    environmentOptions: {
      nuxt: {
        domEnvironment: 'happy-dom',
      },
    },
  },
});
```

## Write tests

The Nuxt SDK fetches content through internal API routes. In tests, you can mock these routes and mount async components using the helpers from [Nuxt Test Utils](https://nuxt.com/docs/getting-started/testing).

### Content rendering

Consider a `HomeHero` component that fetches slot content with a fallback:

**JavaScript**

```vue
<script setup>
const {data, error} = await useContent('home-hero', {
  fallback: {
    title: 'Default title',
    subtitle: 'Default subtitle',
  },
});
</script>

<template>
  <div v-if="data">
    <h1>{{ data.content.title }}</h1>
    <p>{{ data.content.subtitle }}</p>
  </div>
  <div v-else-if="error">
    <p>Something went wrong</p>
  </div>
</template>
```

**TypeScript**

```vue
<script setup lang="ts">
const {data, error} = await useContent('home-hero', {
  fallback: {
    title: 'Default title',
    subtitle: 'Default subtitle',
  },
});
</script>

<template>
  <div v-if="data">
    <h1>{{ data.content.title }}</h1>
    <p>{{ data.content.subtitle }}</p>
  </div>
  <div v-else-if="error">
    <p>Something went wrong</p>
  </div>
</template>
```

You can test both the success and error paths by mocking the content endpoint:

**test/HomeHero.test.ts**

```ts
import {describe, it, expect} from 'vitest';
import {mountSuspended, registerEndpoint} from '@nuxt/test-utils/runtime';
import HomeHero from '../components/HomeHero.vue';

describe('HomeHero', () => {
it('should render the personalized content', async () => {
  registerEndpoint('/api/_croct/content', {
    method: 'POST',
    handler: () => ({
      content: {
        title: 'Personalized title',
        subtitle: 'Personalized subtitle',
      },
    }),
  });

  const wrapper = await mountSuspended(HomeHero);

  expect(wrapper.find('h1').text()).toBe('Personalized title');
  expect(wrapper.find('p').text()).toBe('Personalized subtitle');
});

it('should render the error state on failure', async () => {
  registerEndpoint('/api/_croct/content', {
    method: 'POST',
    handler: () => {
      throw createError({
        statusCode: 500,
        statusMessage: 'Server error',
      });
    },
  });

  const wrapper = await mountSuspended(HomeHero);

  expect(wrapper.text()).toContain('Something went wrong');
});
});
```

### Query evaluation

The same approach works for query evaluation. Consider a `Greeting` component:

**JavaScript**

```vue
<script setup>
const {data: returning} = await useEvaluation("user is returning", {
  fallback: false,
});
</script>

<template>
  <h1 v-if="returning">Welcome back!</h1>
  <h1 v-else>Welcome!</h1>
</template>
```

**TypeScript**

```vue
<script setup lang="ts">
const {data: returning} = await useEvaluation<boolean>("user is returning", {
  fallback: false,
});
</script>

<template>
  <h1 v-if="returning">Welcome back!</h1>
  <h1 v-else>Welcome!</h1>
</template>
```

Mock the evaluation endpoint to control the query result:

**test/Greeting.test.ts**

```ts
import {describe, it, expect} from 'vitest';
import {mountSuspended, registerEndpoint} from '@nuxt/test-utils/runtime';
import Greeting from '../components/Greeting.vue';

describe('Greeting', () => {
it('should greet returning users', async () => {
  registerEndpoint('/api/_croct/evaluate', {
    method: 'POST',
    handler: () => true,
  });

  const wrapper = await mountSuspended(Greeting);

  expect(wrapper.find('h1').text()).toBe('Welcome back!');
});

it('should use the fallback on failure', async () => {
  registerEndpoint('/api/_croct/evaluate', {
    method: 'POST',
    handler: () => {
      throw createError({
        statusCode: 500,
        statusMessage: 'Server error',
      });
    },
  });

  const wrapper = await mountSuspended(Greeting);

  expect(wrapper.find('h1').text()).toBe('Welcome!');
});
});
```

### Event tracking

Tracking is a client-side feature, so these tests use [Vue Test Utils](https://test-utils.vuejs.org/api/#mount) with the `createCroct` plugin from `@croct/plug-nuxt/csr`. This gives you direct access to the event listener API.

> **Automatic test mode detection**
>
> If you are using Vitest or any other test framework that sets `NODE_ENV=test`, the SDK auto-detects the test environment and uses a fake transport layer. You do not need to mock the network requests.

Consider a `TrackButton` component:

**JavaScript**

```vue
<script setup>
import {useCroct} from '@croct/plug-nuxt/csr';

const croct = useCroct();

function onClick() {
  croct.track('goalCompleted', {
    goalId: 'ctaClicked',
  });
}
</script>

<template>
  <button @click="onClick">Click me</button>
</template>
```

**TypeScript**

```vue
<script setup lang="ts">
import {useCroct} from '@croct/plug-nuxt/csr';

const croct = useCroct();

function onClick() {
  croct.track('goalCompleted', {
    goalId: 'ctaClicked',
  });
}
</script>

<template>
  <button @click="onClick">Click me</button>
</template>
```

Mount the component with the Croct plugin and assert on the tracked events:

**test/TrackButton.test.ts**

```ts
import {describe, it, expect, afterEach} from 'vitest';
import {mount} from '@vue/test-utils';
import {createCroct} from '@croct/plug-nuxt/csr';
import croct from '@croct/plug';
import TrackButton from '../components/TrackButton.vue';

describe('TrackButton', () => {
afterEach(async () => {
  await croct.unplug();
});

it('should track a goal on click', async () => {
  const plugin = createCroct({
    appId: '00000000-0000-0000-0000-000000000000',
    test: true,
  });

  const wrapper = mount(TrackButton, {
    global: {plugins: [plugin]},
  });

  const listener = vi.fn();
  croct.tracker.addListener(listener);

  await wrapper.find('button').trigger('click');

  expect(listener).toHaveBeenCalledWith(
    expect.objectContaining({
      status: 'confirmed',
      event: {
        type: 'goalCompleted',
        goalId: 'ctaClicked',
      },
    }),
  );

  croct.tracker.removeListener(listener);
});
});
```

You can find more details about the available events in the [Event reference](/reference/event/overview#event-types).
