> Markdown version of [Testing](https://vaadin.com/docs/next/hilla/guides/testing). Section index: [llms.txt](https://vaadin.com/docs/next/hilla/llms.txt)

# Testing

This guide explains the basics of testing views in Hilla. It uses the [Vitest](https://vitest.dev/) library for testing and setting up a configuration that runs tests in a browser to produce accurate results. It also uses the [React Testing Library](https://testing-library.com/docs/react-testing-library/intro/) to render and interact with React components during tests.

## <a id="setting-up-a-test-environment"></a>Setting Up a Test Environment

Before starting to test, make sure your Vaadin project is initialized, and the default Vite configuration exists. You can do this by starting the application once by running the default Maven goal:

```terminal
mvn
```

Now, install the necessary testing libraries like so:

```terminal
npm install -D vitest @vitest/browser webdriverio pretty-format @testing-library/react @testing-library/user-event
```

Next, create a test configuration file named, `vitest.config.ts` in the project root with the following content:

```typescript
import type { UserConfigFn } from 'vite';
import { overrideVaadinConfig } from './vite.generated';

const customConfig: UserConfigFn = (env) => ({
  plugins: [],
  test: {
    include: ['./tests/**/*.{test,spec}.ts?(x)'],
    globals: true,
    browser: {
      enabled: true,
      name: 'chrome',
    }
  },
});

export default overrideVaadinConfig(customConfig);
```

Finally, add an `npm` script to `package.json` to run the tests and open the Vitest UI:

```json
"scripts": {
  "test": "vitest"
}
```

You can now run the tests by executing the following from the command-line:

```terminal
npm test
```

At this point, this command fails because there aren’t any tests, yet. That’s covered in the next section.

## <a id="writing-tests"></a>Writing Tests

As an example, to see how to test views in Hilla, use a simple Todo view. To start, create a basic view in `frontend/views/TodoView.tsx` like this:

```tsx
export function TodoView() {
  return (
    <div>
      <h2>My Todos</h2>
    </div>
  );
}
```

Now, create a basic test for this view in a file named, `frontend/tests/TodoView.test.tsx` and copy the following into it:

```tsx
import { describe, it, expect } from 'vitest';
import { TodoView } from 'Frontend/views/TodoView';
import { render, screen } from '@testing-library/react';

describe('TodoView', () => {
  it('should render', () => {
    render(<TodoView />);
    expect(screen.getByText('My Todos')).to.exist;
  });
});
```

This test file contains a single test that renders the view and verifies that the `My Todos` text is present in the DOM.

Start the test by executing the following again from the command-line:

```terminal
npm test
```

This opens a browser window and displays the Vitest UI, which can be used for running and monitoring tests. It should run the newly added test and report that it passed.

## <a id="testing-user-interaction"></a>Testing User Interaction

Now you can explore how to add a feature to the view and test it with user interactions. To do this, add an input field and a button to enter new tasks to do, along with a list to render them. Here’s the updated `TodoView` component:

```tsx
import { Button } from '@vaadin/react-components/Button.js';
import {
  TextField,
  TextFieldValueChangedEvent,
} from '@vaadin/react-components/TextField.js';
import { useSignal } from '@vaadin/hilla-react-signals';

export function TodoView() {
  const newTodo = useSignal('');
  const todos = useSignal<Array<string>>([]);

  return (
    <div className="p-m">
      <h2>My Todos</h2>
      <div className="flex gap-s items-baseline">
        <TextField
          label="New todo"
          value={newTodo.value}
          onValueChanged={(e: TextFieldValueChangedEvent) =>
            newTodo.value = e.detail.value
          }
        />
        <Button
          onClick={() => {
            todos.value = [...todos, newTodo];
            newTodo = '';
          }}
        >
          Add todo
        </Button>
      </div>
      <ul>
        {todos.value.map((todo, idx) => (
          <li key={idx}>{todo}</li>
        ))}
      </ul>
    </div>
  );
}
```

Next, add a test that interacts with the view. For testing interactions, import `userEvent` from `@testing-library/user-event` like so:

```tsx
import { userEvent } from '@testing-library/user-event';

describe('TodoView', () => {
  ...

  it('should add a todo', async () => {
    render(<TodoView />);

    // Change the value of the text field
    const textField = screen.getByLabelText('New todo');
    await userEvent.click(textField);
    await userEvent.type(textField, 'Read testing guide');

    // Click the add todo button
    const button = screen.getByText('Add todo');
    await userEvent.click(button);

    // Rerender and check that the todo is shown
    expect(screen.getByText('Read testing guide')).to.exist;
  });
});
```

This new test first finds the text field and changes its value using `userEvent.type`. Next, it finds the button and clicks it using `userEvent.click`. Finally, it verifies that the new todo is rendered in the list.

Save the test so Vitest can run it in the browser, verifying that the interaction works as expected.

## <a id="testing-backend-calls"></a>Testing Backend Calls

Views often need to interact with backend services, which should be tested, as well. In this section, you’ll create a backend service to store todos and then verify that it’s called correctly from the view. First, create a dummy service called, `TodoService.java` next to `Application.java`:

```java
package com.example.application;

import com.vaadin.flow.server.auth.AnonymousAllowed;
import com.vaadin.hilla.BrowserCallable;

@BrowserCallable
@AnonymousAllowed
public class TodoService {
    public void addTodo(String todo) {
        System.out.println("addTodo: " + todo);
    }
}
```

The service has a single method, `addTodo` that only prints the given todo to the console.

From the CLI, run the following command to generate the TypeScript client for the service:

```terminal
mvn clean compile hilla:generate
```

Now, update the click handler of the button in `TodoView` to call the service:

```tsx
/* Add new import for generated service client */
import { TodoService } from 'Frontend/generated/endpoints';

...

<Button
  onClick={() => {
    TodoService.addTodo(newTodo);
    setTodos([...todos, newTodo]);
    setNewTodo('');
  }}
>
  Add todo
</Button>
```

Next, add a test to verify that the service is called correctly. Set up a test environment that stubs the service to prevent actual backend calls and allows you to monitor calls:

```tsx
/* Update imports from vitest */
import { afterEach, beforeEach, describe, expect, it, type SpyInstance, vi } from 'vitest';

/* Add new import for generated service client */
import { TodoService } from 'Frontend/generated/endpoints';

describe('TodoView', () => {
  /* Add test setup and teardown */
  let addTodoSpy: SpyInstance;

  beforeEach(() => {
    addTodoSpy = vi.spyOn(TodoService, 'addTodo');
    addTodoSpy.mockReturnValue(Promise.resolve());
  });

  afterEach(() => {
    addTodoSpy.mockRestore();
  });

  ...

  it('should call service when adding todo', async () => {
    render(<TodoView />);

    const textField = screen.getByLabelText('New todo');
    await userEvent.click(textField);
    await userEvent.type(textField, 'Read testing guide');

    const button = screen.getByText('Add todo');
    await userEvent.click(button);

    expect(addTodoSpy).toHaveBeenCalledWith('Read testing guide');
  });
});
```

In the `beforeEach` hook, this test first creates a spy for the `addTodo` method of the service. Then, this stubs the spy to return a resolved promise to prevent the actual backend call. Finally, it restores the original method in the `afterEach` hook to prevent the stub from leaking to other tests.

The actual test does the same user interaction as before, but this time it’s verifying that the service is called with the correct argument.

Save the test so Vitest can run it in the browser, verifying that the service is called correctly.

## <a id="further-information"></a>Further Information

For more information about Vitest and React Testing Library, refer to the official documentation:

- [Vitest Documentation](https://vitest.dev/guide/)

- [Vitest API Documentation](https://vitest.dev/api/)

- [React Testing Library Documentation](https://testing-library.com/docs/react-testing-library/intro/)

- [React Testing Library API Documentation](https://testing-library.com/docs/react-testing-library/api/)
