Unit testing utility for AngularJS (1.x), heavily inspired by the wonderful Enzyme API. ❤️
Therefore, it is well suited for organisations and individuals moving from AngularJS to React. It is test framework and runner agnostic, but the examples are written using Jest syntax.
An example showing the utility in use can be found here.
Available methods:
mount
mockComponent
Returned classes:
TestElementWrapper
mock
npm install angularjs-enzyme --save-dev
import { mount, mockComponent } from 'angularjs-enzyme';
- Include the script from
node_modules/angularjs-enzyme/dist/angularjs-enzyme.js
. - Use the utility from the global context under the name
angularjsEnzyme
.
Mounts the component with tagName
(String
) and optional props
(Object
) and returns a TestElementWrapper
with numerous helper methods. The props are attached to the $ctrl
available in the template scope. Only components with one-way bound props (<
) can be mounted.
Example
some-component.html
<h1>{{ $ctrl.title }}</h1>
<p>{{ $ctrl.text }}</p>
import 'angular';
import 'angular-mocks';
import { mount } from 'angularjs-enzyme';
describe('Component under test', () => {
let component;
beforeEach(() => {
angular.mock.module('moduleOfComponentUnderTest');
component = mount('some-component', { title: 'A title', text: 'Some text' });
});
});
By default, AngularJS renders the whole component tree. This function mocks a child component with name
(String
) in the component under test and returns a mock
. The child component won't be compiled and its controller won't be invoked, enabling testing the component under test in isolation. In addition, the returned mock
has methods useful for testing.
Example
import 'angular';
import 'angular-mocks';
import { mockComponent } from 'angularjs-enzyme';
describe('Component under test', () => {
let childComponent;
beforeEach(() => {
angular.mock.module('moduleOfComponentUnderTest');
childComponent = mockComponent('child-component'); // ⇦ after module, before inject
});
});
The number of elements in the wrapper.
Example
some-component.html
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
it('has three list items', () => {
expect(component.find('li').length).toBe(3);
});
Returns HTML of the wrapper. It should only be used for logging purposes, in tests other methods should be preferred.
Example
some-component.html
<h1>Some title</h1>
it('renders title as html', () => {
expect(component.html()).toBe('<h1>Some title</h1>');
});
Example
some-component.html
<h1>Some title</h1>
<p>Some text</p>
it('has paragraph text', () => {
expect(component.find('p').text()).toBe('Some text');
});
Returns whether the wrapper has a class with className
(String
) or not.
Example
some-component.html
<button class="success">Pay</button>
it('has success class', () => {
expect(component.find('button').hasClass('success')).toBe(true);
});
it('does not have error class', () => {
expect(component.find('button').hasClass('error')).toBe(false);
});
Returns whether or not the wrapper contains any elements.
Example
some-component.html
<button>Pay</button>
it('has button', () => {
expect(component.find('button').exists()).toBe(true);
});
it('does not have link', () => {
expect(component.find('a').exists()).toBe(false);
});
Returns a TestElementWrapper
(for chaining) with every element matching the selector
(String
).
Example
some-component.html
<div class="left">
<a href="https://neopets.com">Wrong</a>
<a href="https://transferwise.com">Wrong</a>
</div>
<div class="right">
<a href="https://neopets.com">Wrong</a>
<a href="https://transferwise.com">Correct</a>
</div>
it('has one transferwise link with corrext text on the right', () => {
const link = component.find('.right a[href="https://transferwise.com"]');
expect(link.length).toBe(1);
expect(link.text()).toBe('Correct');
});
Returns a TestElementWrapper
(for chaining) for the first element.
Example
some-component.html
<button class="btn btn-primary">Balance</button>
<button class="btn btn-primary">Bank transfer</button>
<button class="btn btn-primary">Card</button>
it('has balance as the first button', () => {
const firstButton = component.find('button').first();
expect(firstButton.text()).toBe('Balance');
});
Returns a TestElementWrapper
(for chaining) for element at index
(Number
).
Example
some-component.html
<button class="btn btn-primary">Balance</button>
<button class="btn btn-primary">Bank transfer</button>
<button class="btn btn-primary">Card</button>
it('has card as third button', () => {
const thirdButton = component.find('button').at(2);
expect(thirdButton.text()).toBe('Card');
});
Maps the nodes in the wrapper to another array using fn
(Function
).
Example
some-component.html
<ul>
<li>One</li>
<li>Two</li>
<li>Three</li>
</ul>
it('has three list items with their number as a word', () => {
const items = component.find('li');
expect(items.map(item => item.text())).toEqual(['One', 'Two', 'Three']);
});
Returns all wrapper props/attributes.
Example
some-component.html
<a href="https://transferwise.com" target="_blank">Send money</a>
it('has transferwise link that opens in a new tab', () => {
expect(component.find('a').props()).toEqual({
href: 'https://transferwise.com',
target: '_blank',
});
});
Returns wrapper prop/attribute value with provided key
(String
).
Example
some-component.html
<a href="https://transferwise.com">Send money</a>
it('has transferwise link', () => {
expect(component.find('a').prop('href')).toBe('https://transferwise.com');
});
Calls an event handler on the wrapper for passed event
with data
(optional), updates the view, and returns wrapper for chaining.
NOTE: event
should be written in camelCase and without the on
present in the event handler name. Currently, change
and click
events are supported, with change
requiring an event format.
Example
some-component.html
<input ng-model="$ctrl.text" />
<p>{{ $ctrl.text }}</p>
<button ng-click="$ctrl.onClick($ctrl.text)">Click me</button>
let component;
let onClick;
beforeEach(() => {
onClick = jest.fn();
component = mount('some-component', { text: 'Original text', onClick });
});
it('calls click handler on button click', () => {
const button = component.find('button');
expect(onClick).not.toBeCalled();
button.simulate('click');
expect(onClick).toBeCalledWith('Original text');
});
it('changes text on input change', () => {
const input = component.find('input');
const text = () => component.find('p').text();
expect(text()).toBe('Original text');
input.simulate('change', { target: { value: 'New text' } });
expect(text()).toBe('New text');
});
Merges props
(Object
) with existing props and updates view to reflect them, returning itself for chaining.
Example
some-component.html
<h1>{{ $ctrl.title }}</h1>
<p>{{ $ctrl.text }}</p>
it('changes title and text when props change', () => {
const component = mount('some-component', {
title: 'Original title',
text: 'Original text',
});
const title = () => component.find('h1').text();
const text = () => component.find('p').text();
expect(title()).toBe('Original title');
expect(text()).toBe('Original text');
component.setProps({ title: 'New title', text: 'New text' });
expect(title()).toBe('New title');
expect(text()).toBe('New text');
});
Returns whether or not the mocked component exists in the rendered template.
Example
some-component.html
<button ng-click="$ctrl.show = !$ctrl.show">
Show child
</button>
<child-component ng-if="$ctrl.show"></child-component>
let component;
beforeEach(() => {
component = mount('some-component');
});
it('allows toggling child component', () => {
const button = component.find('button');
expect(childComponent.exists()).toBe(false);
button.simulate('click');
expect(childComponent.exists()).toBe(true);
button.simulate('click');
expect(childComponent.exists()).toBe(false);
});
Returns all mocked component props.
Example
some-component.html
<div>Something else</div>
<child-component
some-prop="'A string'",
some-other-prop="12345"
></child-component>
let component;
beforeEach(() => {
component = mount('some-component');
});
it('passes props to child component', () => {
expect(childComponent.props()).toEqual({
someProp: 'A string',
someOtherProp: 12345,
});
});
Returns mocked component prop value with the provided key
(String
).
Example
some-component.html
<div>Something else</div>
<child-component some-prop="'A string'"></child-component>
let component;
beforeEach(() => {
component = mount('some-component');
});
it('passes some prop to child component', () => {
expect(childComponent.prop('someProp')).toBe('A string');
});
Calls an event handler on the mocked component for passed event
with data
(optional), updates the view, and returns mocked component for chaining.
NOTE: event
should be written in camelCase and without the on
present in the event handler name. So, to call onSomePropChange
, .simulate('somePropChange')
should be used.
Example
some-component.html
<div>Something else</div>
<child-component
on-some-prop-change="onSomePropChange"
></child-component>
it('calls parent component with data when child component is called', () => {
const onSomePropChange = jest.fn();
mount('some-component', { onSomePropChange });
expect(onSomePropChange).not.toBeCalled();
childComponent.simulate('somePropChange', 'New value');
expect(onSomePropChange).toBeCalledWith('New value');
});
- Run tests with
npm run test:watch
.npm test
will check for package and changelog version match, ESLint and Prettier format in addition. - Bump version number in
package.json
according to semver and add an item that a release will be based on toCHANGELOG.md
. - Submit your pull request from a feature branch and get code reviews.
- If the pull request is approved and the CircleCI build passes, you will be able to squash/rebase and merge.
- Code will automatically be released to GitHub and published to npm according to the version specified in the changelog and
package.json
.
For features and bugs, feel free to add issues or contribute.