What is ember-qunit?
ember-qunit is an Ember.js testing framework that integrates QUnit with Ember.js applications. It provides a set of helpers and utilities to make it easier to write and run tests for Ember.js applications.
What are ember-qunit's main functionalities?
Module Setup
This feature allows you to set up a test module for unit testing. The `setupTest` helper sets up the necessary environment for testing an Ember.js service.
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module('Unit | Service | my-service', function(hooks) {
setupTest(hooks);
test('it exists', function(assert) {
let service = this.owner.lookup('service:my-service');
assert.ok(service);
});
});
Rendering Tests
This feature allows you to write rendering tests for Ember.js components. The `setupRenderingTest` helper sets up the environment for rendering a component and the `render` function is used to render the component in the test.
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
module('Integration | Component | my-component', function(hooks) {
setupRenderingTest(hooks);
test('it renders', async function(assert) {
await render(hbs`<MyComponent />`);
assert.equal(this.element.textContent.trim(), 'expected text');
});
});
Application Tests
This feature allows you to write application tests for Ember.js routes. The `setupApplicationTest` helper sets up the environment for testing an entire Ember.js application, and the `visit` function is used to navigate to a specific route in the test.
import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit';
import { visit, currentURL } from '@ember/test-helpers';
module('Acceptance | my route', function(hooks) {
setupApplicationTest(hooks);
test('visiting /my-route', async function(assert) {
await visit('/my-route');
assert.equal(currentURL(), '/my-route');
});
});
Other packages similar to ember-qunit
ember-mocha
ember-mocha is an alternative testing framework for Ember.js that integrates Mocha with Ember.js applications. It provides similar functionality to ember-qunit but uses Mocha as the testing framework instead of QUnit. This can be useful for developers who prefer Mocha's syntax and features.
ember-cli-qunit
ember-cli-qunit is another package that integrates QUnit with Ember.js applications. It provides a similar set of helpers and utilities as ember-qunit but is specifically designed to work with the Ember CLI build system.
ember-test-helpers
ember-test-helpers is a low-level library that provides a set of helpers for testing Ember.js applications. It is used internally by both ember-qunit and ember-mocha and can be used directly for more fine-grained control over test setup and execution.
Ember QUnit
Ember QUnit simplifies unit testing of Ember applications with QUnit by
providing QUnit-specific wrappers around the helpers contained in
ember-test-helpers.
Usage
Component Integration Tests
import hbs from 'htmlbars-inline-precompile';
import { test, moduleForComponent } from 'ember-qunit';
moduleForComponent('x-foo', {
integration: true
});
test('it renders', function(assert) {
assert.expect(2);
this.set('value', 'cat');
this.on('action', function(result) {
assert.equal(result, 'bar', 'The correct result was returned');
});
this.render(hbs`
{{ x-foo value=value action="result" }}
`);
assert.equal(this.$('div>.value').text(), 'cat', 'The component shows the correct value');
this.$('button').click();
});
Component integration tests are the default mode for moduleForComponent
. You can still explicitly activate them by passing integration: true
.
Integration tests have the advantage of testing your component as Ember would actually use them. It's helpful to think of this mode as simply testing the inputs and outputs of the component. These tests allow you interact with both the bound values that are passed into the component as well as its resulting actions.
Component integration tests have the following features:
- Your test context
this
acts as the outer context for the component. As a result, you can call this.set
and this.on
to setup values and event listeners that you can then have interact with the component. - You are required to render the component as a template, e.g.
this.render(hbs`{{ your-component-name value=value action="updated" }}`)
. You can render other components as well as block content. - All of the normal Ember lifecycle hooks for a component are called (including the new ones from 1.13.x).
- Testing the component's template is through
this.$()
. - You do not require dependencies through
needs:
. Doing so will force the test into unit mode. - You do not have direct access to the component instance. (
this.subject()
will raise an exception).
Component Unit Tests
Ember Guide
import { test, moduleForComponent } from 'ember-qunit';
moduleForComponent('x-foo', {
unit: true,
needs: ['helper:pluralize-string']
});
test('it renders', function(assert) {
assert.expect(1);
var subject = this.subject();
this.render();
assert.equal(this.$('.foo').text(), 'bar');
});
Unit tests used to be the default mode for component tests. To flag a test as a unit test, either specify unit: true
or include needs: []
in the callbacks object.
Unit tests have the advantage of giving you direct access to the component instance so you can test its internals. Unit tests have the following features:
- You have access to the component instance through
this.subject()
. - If you want to render the component's template, call either
this.render()
or this.$()
. - Testing the component's template is through
this.$()
. - You are required to specify any dependencies other than the component's template in the
needs: []
option. This includes helpers, services, partials, and any other components (with their templates) that are referenced. - Unit tests do not call most of the Ember lifecycle hooks.
didInsertElement
and willDestroyElement
will be called, but the remaining hooks introduced in Ember 1.13.x will not be. - There is no outer context for the component so testing things such as actions will require directly stubbing the actions on the component.
Other Tests
Controllers Guide
Routes Guide
import { test, moduleFor } from 'ember-qunit';
moduleFor('controller:home');
test('It can calculate the result', function(assert) {
assert.expect(1);
var subject = this.subject();
subject.set('value', 'foo');
assert.equal(subject.get('result'), 'bar');
});
moduleFor
works for any object you could look up with the Ember Resolver (service, routes, controllers, etc.).
Note: Controllers / Routes do not have access to rendering. You will need to either use a component test or an acceptance test.
Ember Data Tests
Ember Guide
import { test, moduleForModel } from 'ember-qunit';
moduleForModel('user', {
needs: ['model:child']
});
test('It can set its child', function(assert) {
assert.expect(1);
var subject = this.subject();
var child = subject.store.createRecord('child');
subject.get('children').pushObject(child);
assert.equal(subject.get('some-computed-value'), true);
});
Advanced Usage
Setting the resolver
setResolver(Ember.DefaultResolver.create({ namespace: App }));
import Resolver from './path/to/resolver';
import { setResolver } from 'ember-qunit';
setResolver(Resolver.create());
Async Example
Under the hood, if you use Ember.RSVP.Promise
, ember-qunit will call
QUnit's start
and stop
helpers to stop the test from tearing down
and running other tests while your asynchronous code runs. ember-qunit
also asserts that the promise gets fulfilled.
In addition, you can also return promises in the test body:
test('async is awesome', function(assert) {
assert.expect(1);
var myThing = MyThing.create();
return myThing.exampleMethod().then(function() {
assert.ok(myThing.get('finished'));
});
});
If an error is thrown in your promise or a promise
within test
becomes rejected, ember-qunit will fail the test.
To assert that a promise should be rejected, you can "catch"
the error and assert that you got there:
test('sometimes async gets rejected', function(assert) {
assert.expect(1);
var myThing = MyThing.create()
return myThing.exampleMethod().then(function() {
assert.ok(false, "promise should not be fulfilled");
})['catch'](function(err) {
assert.equal(err.message, "User not Authorized");
});
});
Test Helpers
moduleFor(fullName [, description [, callbacks]])
-
fullName
: (String) - The full name of the unit, ie
controller:application
, route:index
.
-
description
: (String) optional - The description of the module
-
callbacks
: (Object) optional
- QUnit callbacks (
beforeEach
and afterEach
) - ember-test-helpers callback (
subject
) integration: true
or unit: true
(default: integration: true
)needs
specify any dependencies the tested module will require.
moduleForComponent(name, [description, callbacks])
-
name
: (String) - the short name of the component that you'd use in a
template, ie x-foo
, ic-tabs
, etc.
-
description
: (String) optional - The description of the module
-
callbacks
: (Object) optional
- QUnit callbacks (
beforeEach
and afterEach
) - ember-test-helpers callback (
subject
) integration: true
or unit: true
(default: integration: true
)needs
specify any dependencies the tested module will require. (Including this will force your test into unit mode).
moduleForModel(name, [description, callbacks])
-
name
: (String) - the short name of the model you'd use in store
operations ie user
, assignmentGroup
, etc.
-
description
: (String) optional - The description of the module
-
callbacks
: (Object) optional
- QUnit callbacks (
beforeEach
and afterEach
) - ember-test-helpers callback (
subject
) integration: true
or unit: true
(default: integration: true
)needs
specify any dependencies the tested module will require.
Contributing
Contributions are welcome. Please follow the instructions below to install and
test this library.
Installation
$ npm install
Testing
In order to test in the browser:
$ npm start
... and then visit http://localhost:4200/tests.
In order to perform a CI test:
$ npm test
Copyright and License
Copyright 2015 Ryan Florence and contributors. MIT License.