Contains stubbing functions for Vue components to unit test components without requiring the full component to be loaded.
When testing Vue components, you often need to test a parent component's behavior without rendering all of its child components. This library provides pre-built stub components that:
- Simplify Component Isolation: Test parent components without worrying about child component complexity or dependencies
- Verify Props Passing: Automatically display props in a testable format (e.g.,
propName-value) - Test Event Handling: Create stubs that emit events with specific payloads, allowing you to verify parent components handle events correctly
- Reduce Test Complexity: Avoid importing and configuring numerous child components in your tests
- Support Multiple Testing Libraries: Works seamlessly with both
@vue/test-utilsand@testing-library/vue
- Testing that a parent component passes the correct props to child components
- Verifying that a parent component correctly handles events emitted by children
- Testing form validation flows where child components expose validation methods
- Isolating unit tests from complex or slow-to-render child components
npm install --save-dev @sourceallies/vue-testing-library-stubsThis project is written in TypeScript.
- Node.js >= 18.0.0
- npm
npm installBuild the TypeScript source to JavaScript:
npm run buildEnsure package is not already installed in the project testing the changes.
npm uninstall @sourceallies/vue-testing-library-stubsEnsure Vue Testing Library Stubs project is using the same node version as the project testing the changes.
node -vCreate a symlink for the project testing the changes to connect to.
npm linkIn project testing the changes link the package
npm link @sourceallies/vue-testing-library-stubsThe compiled output will be in the dist/ directory.
src/
├── index.ts # Main entry point with stub functions
└── types.ts # TypeScript type definitions
This library provides various stub components for testing Vue applications with @vue/test-utils or @testing-library/vue.
Create a simple stub component for testing:
Using @vue/test-utils:
import { mount } from '@vue/test-utils';
import { getStub } from '@sourceallies/vue-testing-library-stubs';
const wrapper = mount(MyParentComponent, {
global: {
stubs: {
...getStub('ChildComponent')
}
}
});
// Verify the stub was rendered
expect(wrapper.text()).toContain('ChildComponent-stub');Using @testing-library/vue:
import { render, screen } from '@testing-library/vue';
import { getStub } from '@sourceallies/vue-testing-library-stubs';
render(MyParentComponent, {
global: {
stubs: {
...getStub('ChildComponent')
}
}
});
// Verify the stub was rendered
expect(screen.getByText('ChildComponent-stub')).toBeVisible();Test components that pass props to child components:
Using @vue/test-utils:
import { mount } from '@vue/test-utils';
import { getStubWithProps } from '@sourceallies/vue-testing-library-stubs';
const wrapper = mount(MyParentComponent, {
global: {
stubs: {
...getStubWithProps('ChildComponent', 'title', 'count', 'config')
}
}
});
// Verify props are displayed (automatically converts to {propName}-{value})
expect(wrapper.text()).toContain('title-Hello');
expect(wrapper.text()).toContain('count-42');Using @testing-library/vue:
import { render, screen } from '@testing-library/vue';
import { getStubWithProps } from '@sourceallies/vue-testing-library-stubs';
render(MyParentComponent, {
global: {
stubs: {
...getStubWithProps('ChildComponent', 'title', 'count', 'config')
}
}
});
// Verify props are displayed (automatically converts to {propName}-{value})
expect(screen.getByText(/title-Hello/)).toBeVisible();
expect(screen.getByText(/count-42/)).toBeVisible();Test components that listen to child component events:
Using @vue/test-utils:
import { mount } from '@vue/test-utils';
import { getEmittingStub } from '@sourceallies/vue-testing-library-stubs';
const wrapper = mount(MyParentComponent, {
global: {
stubs: {
...getEmittingStub('ChildComponent', 'save', { id: 123 })
}
}
});
// Trigger the event by finding the button with text 'save'
const button = wrapper.find('button'); // Button text is 'save'
await button.trigger('click');
// Verify parent component handled the event
expect(wrapper.emitted('save')).toBeTruthy();
expect(wrapper.emitted('save')?.[0]).toEqual([{ id: 123 }]);Using @testing-library/vue:
import { render, screen } from '@testing-library/vue';
import userEvent from '@testing-library/user-event';
import { getEmittingStub } from '@sourceallies/vue-testing-library-stubs';
const { emitted } = render(MyParentComponent, {
global: {
stubs: {
...getEmittingStub('ChildComponent', 'save', { id: 123 })
}
}
});
// Trigger the event
const button = screen.getByRole('button', { name: 'save' });
await userEvent.click(button);
// Verify parent component handled the event with the correct value
expect(emitted()).toHaveProperty('save');
expect(emitted().save[0]).toEqual([{ id: 123 }]);Combine props and event emission:
Using @vue/test-utils:
import { mount } from '@vue/test-utils';
import { getEmittingStubWithProps } from '@sourceallies/vue-testing-library-stubs';
const wrapper = mount(MyParentComponent, {
global: {
stubs: {
...getEmittingStubWithProps(
'ChildComponent',
'update',
[{ newValue: 'test' }],
'title',
'isActive'
)
}
}
});
// Trigger the event
const button = wrapper.find('button'); // Button text is 'update'
await button.trigger('click');
// Verify the event was emitted with the correct value
expect(wrapper.emitted('update')).toBeTruthy();
expect(wrapper.emitted('update')?.[0]).toEqual([{ newValue: 'test' }]);Using @testing-library/vue:
import { render, screen } from '@testing-library/vue';
import userEvent from '@testing-library/user-event';
import { getEmittingStubWithProps } from '@sourceallies/vue-testing-library-stubs';
const { emitted } = render(MyParentComponent, {
global: {
stubs: {
...getEmittingStubWithProps(
'ChildComponent',
'update',
[{ newValue: 'test' }],
'title',
'isActive'
)
}
}
});
// Verify both stub and props are rendered
expect(screen.getByText('ChildComponent-stub')).toBeVisible();
// Trigger the event
const button = screen.getByRole('button', { name: 'update' });
await userEvent.click(button);
// Verify the event was emitted with the correct value
expect(emitted()).toHaveProperty('update');
expect(emitted().update[0]).toEqual([{ newValue: 'test' }]);Test components with multiple event handlers:
Using @vue/test-utils:
import { mount } from '@vue/test-utils';
import { getMultiEmittingStubWithProps, type EmittedEvent } from '@sourceallies/vue-testing-library-stubs';
const events: EmittedEvent[] = [
{ name: 'save', value: { id: 1 } },
{ name: 'cancel' },
{ name: 'delete', value: { id: 1 } }
];
const wrapper = mount(MyParentComponent, {
global: {
stubs: {
...getMultiEmittingStubWithProps('ChildComponent', events, 'title', 'status')
}
}
});
// Each event gets its own button
const buttons = wrapper.findAll('button');
await buttons[0].trigger('click'); // Triggers 'save'
await buttons[1].trigger('click'); // Triggers 'cancel'
// Verify events were emitted with correct values
expect(wrapper.emitted('save')).toBeTruthy();
expect(wrapper.emitted('save')?.[0]).toEqual([{ id: 1 }]);
expect(wrapper.emitted('cancel')).toBeTruthy();
expect(wrapper.emitted('cancel')?.[0]).toEqual([undefined]);Using @testing-library/vue:
import { render, screen } from '@testing-library/vue';
import userEvent from '@testing-library/user-event';
import { getMultiEmittingStubWithProps, type EmittedEvent } from '@sourceallies/vue-testing-library-stubs';
const events: EmittedEvent[] = [
{ name: 'save', value: { id: 1 } },
{ name: 'cancel' },
{ name: 'delete', value: { id: 1 } }
];
const { emitted } = render(MyParentComponent, {
global: {
stubs: {
...getMultiEmittingStubWithProps('ChildComponent', events, 'title', 'status')
}
}
});
// Each event gets its own button
const saveButton = screen.getByRole('button', { name: 'save' });
const cancelButton = screen.getByRole('button', { name: 'cancel' });
await userEvent.click(saveButton);
await userEvent.click(cancelButton);
// Verify events were emitted with correct values
expect(emitted()).toHaveProperty('save');
expect(emitted().save[0]).toEqual([{ id: 1 }]);
expect(emitted()).toHaveProperty('cancel');
expect(emitted().cancel[0]).toEqual([undefined]);Test parent components that call child validation methods:
Using @vue/test-utils:
import { mount } from '@vue/test-utils';
import { getExposedValidateStub } from '@sourceallies/vue-testing-library-stubs';
const wrapper = mount(MyFormComponent, {
global: {
stubs: {
...getExposedValidateStub('InputComponent', true) // true = validation passes
}
}
});
// After calling validate on the child component
expect(wrapper.text()).toContain('InputComponent-stub-validated-true');Using @testing-library/vue:
import { render, screen } from '@testing-library/vue';
import { getExposedValidateStub } from '@sourceallies/vue-testing-library-stubs';
render(MyFormComponent, {
global: {
stubs: {
...getExposedValidateStub('InputComponent', true) // true = validation passes
}
}
});
// After calling validate on the child component
expect(screen.getByText(/InputComponent-stub-validated-true/)).toBeVisible();Test components that use child component's exposed functions:
Using @vue/test-utils:
import { mount } from '@vue/test-utils';
import { getTemplateComponentForExposedFunction } from '@sourceallies/vue-testing-library-stubs';
const props = { value: 'test', enabled: true };
const componentWithExposed = getTemplateComponentForExposedFunction(
ChildComponent,
'reset',
props
);
const wrapper = mount(componentWithExposed, {
props
});
// Click button to trigger the exposed function
await wrapper.find('button').trigger('click');Using @testing-library/vue:
import { render, screen } from '@testing-library/vue';
import userEvent from '@testing-library/user-event';
import { getTemplateComponentForExposedFunction } from '@sourceallies/vue-testing-library-stubs';
const props = { value: 'test', enabled: true };
const componentWithExposed = getTemplateComponentForExposedFunction(
ChildComponent,
'reset',
props
);
render(componentWithExposed, {
props
});
// Click button to trigger the exposed function
const button = screen.getByRole('button', { name: 'reset' });
await userEvent.click(button);Creates a basic stub component with no props or events.
Parameters:
componentName(string): The name of the component to stub
Returns: An object with a single key (the component name) containing the stub configuration
Example:
const stub = getStub('MyComponent');
// Renders: <div>MyComponent-stub</div>Creates a stub component that displays its props in a testable format.
Parameters:
componentName(string): The name of the component to stub...props(string | object): The prop names to accept and display
Returns: An object with a single key (the component name) containing the stub configuration
Features:
- Automatically converts hyphenated prop names to camelCase
- Displays props as
propName-valuein the rendered output - Handles object props by JSON stringifying them
- Shows function names for array of functions (useful for validation rules)
Example:
const stub = getStubWithProps('MyComponent', 'title', 'count', 'is-active');
// Renders props as: title-Hello, count-42, isActive-trueCreates a stub component that emits a single event when a button is clicked.
Parameters:
componentName(string): The name of the component to stubemittedEvent(string): The name of the event to emit...emittedValues(any[]): The values to emit with the event
Returns: An object with a single key (the component name) containing the stub configuration
Features:
- Creates a button with the event name as its label
- Emits the specified event with the provided values when clicked
- Includes
validate(),reset(), andresetValidation()methods that return valid results
Example:
const stub = getEmittingStub('MyComponent', 'save', { id: 123 });
// Button click emits: 'save' event with { id: 123 }getEmittingStubWithProps(componentName: string, emittedEvent: string, emittedValues: any[], ...props: (string | object)[])
Creates a stub component that combines props display and event emission.
Parameters:
componentName(string): The name of the component to stubemittedEvent(string): The name of the event to emitemittedValues(any[]): Array of values to emit with the event...props(string | object): The prop names to accept and display
Returns: An object with a single key (the component name) containing the stub configuration
Features:
- Combines functionality of
getStubWithPropsandgetEmittingStub - Displays props in testable format
- Emits events with specified values
- Includes validation methods
Example:
const stub = getEmittingStubWithProps('MyComponent', 'update', [{ newValue: 'test' }], 'title', 'isActive');getMultiEmittingStubWithProps(componentName: string, events: EmittedEvent[], ...props: (string | object)[])
Creates a stub component that can emit multiple different events.
Parameters:
componentName(string): The name of the component to stubevents(EmittedEvent[]): Array of event configurations withnameand optionalvalue...props(string | object): The prop names to accept and display
Type Definitions:
type EmittedEvent = {
name: string;
value?: any;
};Returns: An object with a single key (the component name) containing the stub configuration
Features:
- Creates a separate button for each event
- Each button is labeled with its event name
- Supports events with or without values
Example:
const events: EmittedEvent[] = [
{ name: 'save', value: { id: 1 } },
{ name: 'cancel' },
{ name: 'delete', value: { id: 1 } }
];
const stub = getMultiEmittingStubWithProps('MyComponent', events, 'title');Creates a stub component with an exposed validate() method for testing form validation.
Parameters:
componentName(string): The name of the component to stubisValid(boolean, optional): Whether validation should pass (default:true)
Returns: An object with a single key (the component name) containing the stub configuration
Features:
- Exposes a
validate()method that returns{ valid: boolean, errors: [] } - Updates the rendered output to show
{componentName}-stub-validated-trueorfalse - Includes
resetValidation()method
Example:
const stub = getExposedValidateStub('InputComponent', true);
// After calling validate(): renders "InputComponent-stub-validated-true"getExposedValidateStubWithProps(componentName: string, isValid: boolean = true, ...props: (string | object)[])
Creates a stub component that combines validation testing with props display.
Parameters:
componentName(string): The name of the component to stubisValid(boolean, optional): Whether validation should pass (default:true)...props(string | object): The prop names to accept and display
Returns: An object with a single key (the component name) containing the stub configuration
Features:
- Combines functionality of
getExposedValidateStubandgetStubWithProps - Displays props in testable format
- Exposes validation methods
Example:
const stub = getExposedValidateStubWithProps('InputComponent', true, 'value', 'label');getTemplateComponentForExposedFunction(componentAlias: Component, exposedFn: string, props?: object)
Creates a wrapper component that calls an exposed function on a child component.
Parameters:
componentAlias(Component): The actual component to wrapexposedFn(string): The name of the exposed function to callprops(object, optional): Props to pass to the child component
Returns: A Vue component configuration that wraps the provided component
Features:
- Creates a button that triggers the exposed function
- Button is labeled with the function name
- Passes props to the child component
- Useful for testing components that use
expose()ordefineExpose()
Example:
const wrapper = getTemplateComponentForExposedFunction(ChildComponent, 'reset', { value: 'test' });
// Renders ChildComponent with a button that calls reset() when clickedMIT