# Unit testing

Learn how to write unit tests for your integration.

The Vue SDK includes a test mode that makes it easy to develop and test your integration by simulating SDK behavior without sending real API requests.

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.

## Enable test mode

Since everything in the SDK is event-driven, the best way to test your integration is to listen for events and make assertions on them.

Even imperative methods — like [`identify`](/reference/sdk/javascript/api/plug/identify), which identifies users, or [`user.edit`](/reference/sdk/javascript/api/user/edit), used to edit user profiles — are just syntactic sugar for triggering events. This approach simplifies testing because you do not have to rely on mocking, which usually results in brittle tests.

To make testing easier, the SDK provides a test mode that you can enable in your test environment.

> **Automatic test mode detection**
>
> If you're using [Jest](https://jestjs.io) or [Vitest](https://vitest.dev) or any other test framework that sets the `NODE_ENV=test`, it should just work out of the box.

By default, the SDK auto-detects test environments based on the `NODE_ENV`.

To explicitly enable test mode, pass [`test`](/reference/sdk/vue/api/functions/create-croct#test-prop) as `true` when initializing the SDK.

Once enabled, the SDK uses a fake transport layer that simulates successful calls, so you do not need to mock the network requests.

## Write tests

With test mode enabled, you can write assertions on the events triggered by your integration.

Suppose you want to check if your application correctly collects user interests. The code you want to test might look like this:

```ts
croct.user.edit()
  .add('interest', 'tests')
  .save()
```

This code triggers a [`userProfileChanged`](/reference/event/types/user-account/user-profile-changed) event with your changes to the user profile, which you can use to make assertions.

Here is an example of how you can write a test for this code using [Vitest](https://vitest.dev):

**JavaScript**

```js
import {mount} from '@vue/test-utils';
import {createCroct} from '@croct/plug-vue';
import croct from '@croct/plug';
import LoginForm from './LoginForm.vue';

it('should add an interest to the user profile', async () => {
const plugin = createCroct({appId: '00000000-0000-0000-0000-000000000000'});

const wrapper = mount(LoginForm, {
  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: 'userProfileChanged',
      patch: {
        operations: [
          {
            path: 'interest',
            type: 'add',
            value: 'tests',
          },
        ],
      },
    },
  }),
);
});
```

**TypeScript**

```ts
import {mount} from '@vue/test-utils';
import {createCroct} from '@croct/plug-vue';
import croct from '@croct/plug';
import type {EventInfo} from '@croct/plug/sdk/tracking';
import LoginForm from './LoginForm.vue';

it('should add an interest to the user profile', async () => {
const plugin = createCroct({appId: '00000000-0000-0000-0000-000000000000'});

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

const listener = vi.fn();

croct.tracker.addListener(listener);

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

expect(listener).toHaveBeenCalledWith(
  expect.objectContaining<Partial<EventInfo<'userProfileChanged'>>>({
    status: 'confirmed',
    event: {
      type: 'userProfileChanged',
      patch: {
        operations: [
          {
            path: 'interest',
            type: 'add',
            value: 'tests',
          },
        ],
      },
    },
  }),
);
});
```

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

### Content retrieval testing

For testing if your content renders correctly, you can provide a mock implementation of the SDK via the `CROCT_INJECTION_KEY` symbol. This isolates your component from the real SDK and gives you full control over what content is returned.

The TypeScript examples below use the [`SlotContent`](/reference/sdk/vue/api/types/slot-content) type to ensure mock data matches the slot's schema.

Suppose you have a `HomeHero.vue` component like this:

**JavaScript**

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

const props = defineProps({
fallbackContent: {
  type: Object,
  required: true,
},
});

const {data: content} = useContent('home-hero@1', {
initial: null,
fallback: props.fallbackContent,
});
</script>

<template>
<div v-if="content === null">Personalizing...</div>
<div v-else>
  <h1>{{ content.title }}</h1>
  <p>{{ content.subtitle }}</p>
  <a :href="content.button.link">{{ content.button.label }}</a>
</div>
</template>
```

**TypeScript**

```vue
<script setup lang="ts">
import {useContent} from '@croct/plug-vue';
import type {SlotContent} from '@croct/plug-vue';

const props = defineProps<{
fallbackContent: SlotContent<'home-hero'>;
}>();

const {data: content} = useContent('home-hero@1', {
initial: null,
fallback: props.fallbackContent,
});
</script>

<template>
<div v-if="content === null">Personalizing...</div>
<div v-else>
  <h1>{{ content.title }}</h1>
  <p>{{ content.subtitle }}</p>
  <a :href="content.button.link">{{ content.button.label }}</a>
</div>
</template>
```

Here is an example of how you can test the dynamic content using [Vitest](https://vitest.dev) and [Vue Test Utils](https://test-utils.vuejs.org):

**JavaScript**

```js
import {mount, flushPromises} from '@vue/test-utils';
import {CROCT_INJECTION_KEY} from '@croct/plug-vue';
import HomeHero from './HomeHero.vue';

it('should render the personalized content on success', async () => {
const content = {
  title: 'Banner title',
  subtitle: 'Banner subtitle',
  button: {
    label: 'Button',
    link: 'https://croct.com',
  },
};

const mockFetch = vi.fn().mockResolvedValue({content: content});

const wrapper = mount(HomeHero, {
  props: {
    fallbackContent: {
      title: 'Fallback title',
      subtitle: 'Fallback subtitle',
      button: {
        label: 'Fallback button',
        link: 'https://croct.com',
      },
    },
  },
  global: {
    provide: {
      [CROCT_INJECTION_KEY]: {
        plug: {fetch: mockFetch},
      },
    },
  },
});

expect(mockFetch).toHaveBeenCalledWith('home-hero@1', expect.any(Object));

await flushPromises();

expect(wrapper.find('h1').text()).toBe(content.title);
expect(wrapper.find('p').text()).toBe(content.subtitle);
expect(wrapper.find('a').text()).toBe(content.button.label);
});
```

**TypeScript**

```ts
import {mount, flushPromises} from '@vue/test-utils';
import {CROCT_INJECTION_KEY} from '@croct/plug-vue';
import type {SlotContent} from '@croct/plug-vue';
import HomeHero from './HomeHero.vue';

it('should render the personalized content on success', async () => {
const content: SlotContent<'home-hero'> = {
  title: 'Banner title',
  subtitle: 'Banner subtitle',
  button: {
    label: 'Button',
    link: 'https://croct.com',
  },
};

const mockFetch = vi.fn().mockResolvedValue({content: content});

const wrapper = mount(HomeHero, {
  props: {
    fallbackContent: {
      title: 'Fallback title',
      subtitle: 'Fallback subtitle',
      button: {
        label: 'Fallback button',
        link: 'https://croct.com',
      },
    },
  },
  global: {
    provide: {
      [CROCT_INJECTION_KEY]: {
        plug: {fetch: mockFetch},
      },
    },
  },
});

expect(mockFetch).toHaveBeenCalledWith('home-hero@1', expect.any(Object));

await flushPromises();

expect(wrapper.find('h1').text()).toBe(content.title);
expect(wrapper.find('p').text()).toBe(content.subtitle);
expect(wrapper.find('a').text()).toBe(content.button.label);
});
```

To test your fallbacks, you can mock a failed response:

**JavaScript**

```js
import {mount, flushPromises} from '@vue/test-utils';
import {CROCT_INJECTION_KEY} from '@croct/plug-vue';
import HomeHero from './HomeHero.vue';

it('should render the fallback content on error', async () => {
const fallbackContent = {
  title: 'Fallback title',
  subtitle: 'Fallback subtitle',
  button: {
    label: 'Fallback button',
    link: 'https://croct.com',
  },
};

// When fallback is provided, the SDK resolves with it on API failure
const mockFetch = vi.fn().mockResolvedValue({content: fallbackContent});

const wrapper = mount(HomeHero, {
  props: {fallbackContent},
  global: {
    provide: {
      [CROCT_INJECTION_KEY]: {
        plug: {fetch: mockFetch},
      },
    },
  },
});

expect(mockFetch).toHaveBeenCalledWith('home-hero@1', expect.any(Object));

await flushPromises();

expect(wrapper.find('h1').text()).toBe(fallbackContent.title);
});
```

**TypeScript**

```ts
import {mount, flushPromises} from '@vue/test-utils';
import {CROCT_INJECTION_KEY} from '@croct/plug-vue';
import type {SlotContent} from '@croct/plug-vue';
import HomeHero from './HomeHero.vue';

it('should render the fallback content on error', async () => {
const fallbackContent: SlotContent<'home-hero'> = {
  title: 'Fallback title',
  subtitle: 'Fallback subtitle',
  button: {
    label: 'Fallback button',
    link: 'https://croct.com',
  },
};

// When fallback is provided, the SDK resolves with it on API failure
const mockFetch = vi.fn().mockResolvedValue({content: fallbackContent});

const wrapper = mount(HomeHero, {
  props: {fallbackContent},
  global: {
    provide: {
      [CROCT_INJECTION_KEY]: {
        plug: {fetch: mockFetch},
      },
    },
  },
});

expect(mockFetch).toHaveBeenCalledWith('home-hero@1', expect.any(Object));

await flushPromises();

expect(wrapper.find('h1').text()).toBe(fallbackContent.title);
});
```

### Query evaluation testing

To test if your queries are working as expected, you can follow the same approach as for content retrieval testing.

Suppose you have a `Greetings.vue` component like this:

**JavaScript**

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

const {data: returning} = useEvaluation('user is returning', {
initial: null,
fallback: false,
});
</script>

<template>
<div v-if="returning === null">Personalizing...</div>
<h1 v-else>{{ returning ? 'Welcome back!' : 'Welcome!' }}</h1>
</template>
```

**TypeScript**

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

const {data: returning} = useEvaluation<boolean | null>('user is returning', {
initial: null,
fallback: false,
});
</script>

<template>
<div v-if="returning === null">Personalizing...</div>
<h1 v-else>{{ returning ? 'Welcome back!' : 'Welcome!' }}</h1>
</template>
```

Here is an example of how you can test the query evaluation using [Vitest](https://vitest.dev) and [Vue Test Utils](https://test-utils.vuejs.org):

**Greetings.test.js**

```js
import {mount, flushPromises} from '@vue/test-utils';
import {CROCT_INJECTION_KEY} from '@croct/plug-vue';
import Greetings from './Greetings.vue';

it('should render the content based on the query result', async () => {
const mockEvaluate = vi.fn().mockResolvedValue(true);

const wrapper = mount(Greetings, {
  global: {
    provide: {
      [CROCT_INJECTION_KEY]: {
        plug: {evaluate: mockEvaluate},
      },
    },
  },
});

expect(mockEvaluate).toHaveBeenCalledWith('user is returning', expect.any(Object));

await flushPromises();

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

To test your fallbacks, you can mock a failed response:

**Greetings.test.js**

```js
import {mount, flushPromises} from '@vue/test-utils';
import {CROCT_INJECTION_KEY} from '@croct/plug-vue';
import Greetings from './Greetings.vue';

it('should render the fallback content on error', async () => {
const mockEvaluate = vi.fn().mockRejectedValue(new Error('Failed'));

const wrapper = mount(Greetings, {
  global: {
    provide: {
      [CROCT_INJECTION_KEY]: {
        plug: {evaluate: mockEvaluate},
      },
    },
  },
});

expect(mockEvaluate).toHaveBeenCalledWith('user is returning', expect.any(Object));

await flushPromises();

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