Zero config
A single render() call starts the Application, registers the controller, mounts the fixture, and waits for connect(). That is the entire setup.
Mount a controller, simulate user interactions, assert the DOM — in three lines. No Application boilerplate, no MutationObserver timing, no cleanup ceremony.
import { render, attr.controller, attr.target, attr.action } from '@tito10047/stimulus-test-utils'
import { expect, test } from 'vitest'
import HelloController from './hello_controller.js'
test('greets by name', async () => {
const { controller, user, element, getByRole } = await render(HelloController, {
html: `
<div ${attr.controller('hello', { greeting: 'Hi' })}>
<input ${attr.target('hello', 'name')} />
<button ${attr.action('hello', 'greet', 'click')}>Greet</button>
<span ${attr.target('hello', 'output')}></span>
</div>
`,
})
await user.type(element.querySelector('input')!, 'Ada')
await user.click(getByRole('button', { name: 'Greet' }))
expect(controller.outputTarget.textContent).toBe('Hi, Ada!')
})That is every line you need: no Application.start(), no document.body.innerHTML = …, no manual await nextTick().
Ready to install it? Continue with Getting Started.