Testing Services
Intern support BrowserStack , SauceLabs and TestingBot run tests remotely on other services. You can use one of these services, register for an account and credentials to cli-test-intern. By default, all testing services will run tests on IE11, Firefox and Chrome and other browsers.
BrowserStack
Use BrowserStack service, the need to provide access key and user name. Access key and user name can be specified on the command line or as an environment variable, see Intern documents .
dojo test -a -c browserstack -k <accesskey> --userName <username>
Or use environment variables
BROWSERSTACK_USERNAME=<username> BROWSERSTACK_ACCESS_KEY=<key> dojo test -a -c browserstack
SauceLabs
Use SauceLabs service, the need to provide access key and user name. Access key and user name can be specified on the command line or as an environment variable, see Intern documents .
dojo test -a -c saucelabs -k <accesskey> --userName <username>
Or use environment variables
SAUCE_USERNAME=<username> SAUCE_ACCESS_KEY=<key> dojo test -a -c saucelabs
TestingBot
Use TestingBot service, the need for key and secret. Key and secret can be specified on the command line or as an environment variable, see Intern documents .
dojo test -a -c testingbot -k <key> -s <secret>
Or use environment variables
TESTINGBOT_SECRET=<secret> TESTINGBOT_KEY=<key> dojo test -a -c saucelabs
harness
When you are using @dojo/framework/testing
, the harness()
most important API, mainly used to set each test and provide context for the implementation of a virtual DOM assertions and interaction. When the object is updated properties
or children
, when component failure and, mirroring the behavior of the core member, and does not require any special or custom logic.
API
interface HarnessOptions {
customComparators?: CustomComparator[];
middleware?: [MiddlewareResultFactory<any, any, any>, MiddlewareResultFactory<any, any, any>][];
}
harness(renderFunction: () => WNode, customComparators?: CustomComparator[]): Harness;
harness(renderFunction: () => WNode, options?: HarnessOptions): Harness;
renderFunction
: Function that returns the UUT WNodecustomComparators
: Custom a group descriptor. Each descriptor is a function of the comparator, for comparingselector
andproperty
targeting ofproperties
options
: Harness expansion options, includingcustomComparators
and a set of middleware / mocks tuples.
harness function returns an Harness
object that provides several API to interact with the unit under test:
Harness
expect
: Perform a complete rendering results assertion of the unit under testexpectPartial
: Performing partial rendering results assertion UUTtrigger
: Used to trigger functions on the device under test nodegetRender
: The index provided by the corresponding renderer returns from the harness
Using @dojo/framework/core
the w()
function generating means for testing is a very simple:
const { describe, it } = intern.getInterface('bdd');
import WidgetBase from '@dojo/framework/widget-core/WidgetBase';
import harness from '@dojo/framework/testing/harness';
import { w, v } from '@dojo/framework/widget-core/d';
class MyWidget extends WidgetBase<{ foo: string }> {
protected render() {
const { foo } = this.properties;
return v('div', { foo }, this.children);
}
}
const h = harness(() => w(MyWidget, { foo: 'bar' }, ['child']));
Shown, harness supports the following functions tsx
. README document remaining examples use the programmatic w()
API, in unit testing can see more tsx
examples.
const h = harness(() => <MyWidget foo="bar">child</MyWidget>);
renderFunction
Delay is performed, it is possible to include additional logic to assert the operation member between properties
and children
.
describe('MyWidget', () => {
it('renders with foo correctly', () => {
let foo = 'bar';
const h = harness(() => {
return w(MyWidget, { foo }, [ 'child' ]));
};
h.expect(/** assertion that includes bar **/);
// update the property that is passed to the widget
foo = 'foo';
h.expect(/** assertion that includes foo **/)
});
});
Mocking Middleware
When initializing harness, mock intermediate can be specified as HarnessOptions
part of the value. Mock Middleware is defined as the tuple of the original and implementations mock middleware middleware thereof. Mock the same way to create middleware and other middleware.
import myMiddleware from './myMiddleware';
import myMockMiddleware from './myMockMiddleware';
import harness from '@dojo/framework/testing/harness';
import MyWidget from './MyWidget';
describe('MyWidget', () => {
it('renders', () => {
const h = harness(() => <MyWidget />, { middleware: [[myMiddleware, myMockMiddleware]] });
h.expect(/** assertion that executes the mock middleware instead of the normal middleware **/);
});
});
Harness automatically mock many of the core middleware and injected into any of their middleware needs:
- invalidator
- setProperty
- destroy
Dojo Mock Middleware
When tested using the Dojo member middleware, the middleware may be used there are many mock. Mock will export a factory, the factory will create a limited scope mock middleware will be used in each test.
Mock node
Middleware
Use @dojo/framework/testing/mocks/middleware/node
of the createNodeMock
can a mock intermediate node. To set the desired value returned from the node mock, call the mock node middleware created and incoming key
and desired DOM node.
import createNodeMock from '@dojo/framework/testing/mocks/middleware/node';
// create the mock node middleware
const mockNode = createNodeMock();
// create a mock DOM node
const domNode = {};
// call the mock middleware with a key and the DOM
// to return.
mockNode('key', domNode);
Mock intersection
Middleware
Use @dojo/framework/testing/mocks/middleware/intersection
of the createIntersectionMock
can a mock intersection middleware. To set the desired value returned from the intersection mock, call the mock created intersection middleware, and pass key
intersection details and expectations.
Consider the following components:
import { create, tsx } from '@dojo/framework/core/vdom';
import intersection from '@dojo/framework/core/middleware/intersection';
const factory = create({ intersection });
const App = factory(({ middleware: { intersection } }) => {
const details = intersection.get('root');
return <div key="root">{JSON.stringify(details)}</div>;
});
Use mock intersection middleware:
import { tsx } from '@dojo/framework/core/vdom';
import createIntersectionMock from '@dojo/framework/testing/mocks/middleware/intersection';
import intersection from '@dojo/framework/core/middleware/intersection';
import harness from '@dojo/framework/testing/harness';
import MyWidget from './MyWidget';
describe('MyWidget', () => {
it('test', () => {
// create the intersection mock
const intersectionMock = createIntersectionMock();
// pass the intersection mock to the harness so it knows to
// replace the original middleware
const h = harness(() => <App key="app" />, { middleware: [[intersection, intersectionMock]] });
// call harness.expect as usual, asserting the default response
h.expect(() => <div key="root">{`{"intersectionRatio":0,"isIntersecting":false}`}</div>);
// use the intersection mock to set the expected return
// of the intersection middleware by key
intersectionMock('root', { isIntersecting: true });
// assert again with the updated expectation
h.expect(() => <div key="root">{`{"isIntersecting": true }`}</div>);
});
});
Mock resize
Middleware
Use @dojo/framework/testing/mocks/middleware/resize
of the createResizeMock
can resize a mock middleware. To set the desired value returned from the resize mock, call the mock resize middleware to create and pass key
a rectangular area to accommodate the content and expectations.
const mockResize = createResizeMock();
mockResize('key', { width: 100 });
Consider the following components:
import { create, tsx } from '@dojo/framework/core/vdom'
import resize from '@dojo/framework/core/middleware/resize'
const factory = create({ resize });
export const MyWidget = factory(function MyWidget({ middleware }) => {
const { resize } = middleware;
const contentRects = resize.get('root');
return <div key="root">{JSON.stringify(contentRects)}</div>;
});
Use mock resize middleware:
import { tsx } from '@dojo/framework/core/vdom';
import createResizeMock from '@dojo/framework/testing/mocks/middleware/resize';
import resize from '@dojo/framework/core/middleware/resize';
import harness from '@dojo/framework/testing/harness';
import MyWidget from './MyWidget';
describe('MyWidget', () => {
it('test', () => {
// create the resize mock
const resizeMock = createResizeMock();
// pass the resize mock to the harness so it knows to replace the original
// middleware
const h = harness(() => <App key="app" />, { middleware: [[resize, resizeMock]] });
// call harness.expect as usual
h.expect(() => <div key="root">null</div>);
// use the resize mock to set the expected return of the resize middleware
// by key
resizeMock('root', { width: 100 });
// assert again with the updated expectation
h.expect(() => <div key="root">{`{"width":100}`}</div>);
});
});
Mock Store
Middleware
Use @dojo/framework/testing/mocks/middleware/store
of createMockStoreMiddleware
can store a strongly typed mock middleware also supports mock process. To store a mock of the process, a tuple can be passed from the original and store Process stub process thereof. Middleware will instead call stub, instead of calling the original process. If you do not pass stub, the middleware will stop calling all of the process.
To modify the value mock store, you need to call mockStore
and pass a set of store operations function returns. This will store the injection path
function, to create a point to modify the state of a pointer.
mockStore((path) => [replace(path('details', { id: 'id' })]);
Consider the following components:
src/MyWidget.tsx
import { create, tsx } from '@dojo/framework/core/vdom'
import { myProcess } from './processes';
import MyState from './interfaces';
// application store middleware typed with the state interface
// Example: `const store = createStoreMiddleware<MyState>();`
import store from './store';
const factory = create({ store }).properties<{ id: string }>();
export default factory(function MyWidget({ properties, middleware: store }) {
const { id } = properties();
const { path, get, executor } = store;
const details = get(path('details');
let isLoading = get(path('isLoading'));
if ((!details || details.id !== id) && !isLoading) {
executor(myProcess)({ id });
isLoading = true;
}
if (isLoading) {
return <Loading />;
}
return <ShowDetails {...details} />;
});
Use mock store Middleware:
tests/unit/MyWidget.tsx
import { tsx } from '@dojo/framework/core/vdom'
import createMockStoreMiddleware from '@dojo/framework/testing/mocks/middleware/store';
import harness from '@dojo/framework/testing/harness';
import { myProcess } from './processes';
import MyWidget from './MyWidget';
import MyState from './interfaces';
import store from './store';
// import a stub/mock lib, doesn't have to be sinon
import { stub } from 'sinon';
describe('MyWidget', () => {
it('test', () => {
const properties = {
id: 'id'
};
const myProcessStub = stub();
// type safe mock store middleware
// pass through an array of tuples `[originalProcess, stub]` for mocked processes
// calls to processes not stubbed/mocked get ignored
const mockStore = createMockStoreMiddleware<MyState>([[myProcess, myProcessStub]]);
const h = harness(() => <MyWidget {...properties} />, {
middleware: [store, mockStore]
});
h.expect(/* assertion template for `Loading`*/);
// assert again the stubbed process
expect(myProcessStub.calledWith({ id: 'id' })).toBeTruthy();
mockStore((path) => [replace(path('isLoading', true)]);
h.expect(/* assertion template for `Loading`*/);
expect(myProcessStub.calledOnce()).toBeTruthy();
// use the mock store to apply operations to the store
mockStore((path) => [replace(path('details', { id: 'id' })]);
mockStore((path) => [replace(path('isLoading', true)]);
h.expect(/* assertion template for `ShowDetails`*/);
properties.id = 'other';
h.expect(/* assertion template for `Loading`*/);
expect(myProcessStub.calledTwice()).toBeTruthy();
expect(myProcessStub.secondCall.calledWith({ id: 'other' })).toBeTruthy();
mockStore((path) => [replace(path('details', { id: 'other' })]);
h.expect(/* assertion template for `ShowDetails`*/);
});
});
Custom Comparators
In some cases, we do not know the exact value of the property during the test, so it is necessary to use a custom comparison descriptors (custom compare descriptor).
Descriptor has a virtual node location to be examined for selector
a custom application and a comparison of attribute names, and receives the actual value returns a boolean predicate result of the comparator function.
const compareId = {
selector: '*', // all nodes
property: 'id',
comparator: (value: any) => typeof value === 'string' // checks the property value is a string
};
const h = harness(() => w(MyWidget, {}), [compareId]);
For all the assertions returned harness
API will only id
property use comparator
for testing, rather than the standard equality test.
selectors
harness
API supports CSS style selectors concept, to assert to locate and operate a virtual nodes in the DOM. View support for a complete list of selectors for more information.
In addition to the standard API also provides:
- Support the positioning node
key
attributes abbreviated@
notation - When using the standard
.
time to locate the pattern classes, usingclasses
properties insteadclass
Properties
harness.expect
Testing is the most common requirement is part of the assertion render
output structure of the function. expect
Receiving a return result of rendering a desired component under test as a function of the parameters.
API
expect(expectedRenderFunction: () => DNode | DNode[], actualRenderFunction?: () => DNode | DNode[]);
expectedRenderFunction
: Returning node desiredDNode
function structureactualRenderFunction
: An optional function, returns the actual assertedDNode
structure
h.expect(() =>
v('div', { key: 'foo' }, [w(Widget, { key: 'child-widget' }), 'text node', v('span', { classes: ['class'] })])
);
expect
You may also receive a second optional parameter and returns the result to the rendering function of the assertion.
h.expect(() => v('div', { key: 'foo' }), () => v('div', { key: 'foo' }));
If the actual rendering of the rendered output and a desired output different throw an exception, and visualized using a structured approach, using (A)
(actual value) and the (E)
(expected value) indicates that all the different points.
Example assertion failure output:
v('div', {
'classes': [
'root',
(A) 'other'
(E) 'another'
],
'onclick': 'function'
}, [
v('span', {
'classes': 'span',
'id': 'random-id',
'key': 'label',
'onclick': 'function',
'style': 'width: 100px'
}, [
'hello 0'
])
w(ChildWidget, {
'id': 'random-id',
'key': 'widget'
})
w('registry-item', {
'id': true,
'key': 'registry'
})
])
harness.trigger
harness.trigger()
In selector
calling on the node location name
specified function.
interface FunctionalSelector {
(node: VNode | WNode): undefined | Function;
}
trigger(selector: string, functionSelector: string | FunctionalSelector, ...args: any[]): any;
selector
: Used to find the target node selectorfunctionSelector
: Either the name of the function is called to find the properties from the node, or a function that returns a function selector from the attribute nodeargs
: In order to locate the parameters passed to the function
If you have a return result, return is the result of the function is triggered.
Example usage:
// calls the `onclick` function on the first node with a key of `foo`
h.trigger('@foo', 'onclick');
// calls the `customFunction` function on the first node with a key of `bar` with an argument of `100`
// and receives the result of the triggered function
const result = h.trigger('@bar', 'customFunction', 100);
functionalSelector
Member function returns properties. Function will be triggered, and the use of ordinary strings functionSelector
way of the same.
Example usage:
Assuming the following VDOM tree structure:
v(Toolbar, {
key: 'toolbar',
buttons: [
{
icon: 'save',
onClick: () => this._onSave()
},
{
icon: 'cancel',
onClick: () => this._onCancel()
}
]
});
And you want to trigger the save button onClick
function.
h.trigger('@buttons', (renderResult: DNode<Toolbar>) => {
return renderResult.properties.buttons[0].onClick;
});
Note: If you can not find the specified selector, then trigger
throws an error.
harness.getRender
harness.getRender()
Returns the index of the specified renderer, if no index of the last renderer is returned.
getRender(index?: number);
index
: To return to the renderer index
Example usage:
// Returns the result of the last render
const render = h.getRender();
// Returns the result of the render for the index provided
h.getRender(1);
Assertion Templates
Assertion template (assertion template) allows you to build the desired rendering functions for incoming h.expect()
in. The idea behind the assertion template from the often asserted that the entire render output, and the need to revise certain parts of the assertion.
To assert template, you need to import the module:
import assertionTemplate from '@dojo/framework/testing/assertionTemplate';
Then, in your tests, you can write a basic assertion, it is the default rendering state components:
Consider the following components:
src/widgets/Profile.ts
import WidgetBase from '@dojo/framework/widget-core/WidgetBase';
import { v } from '@dojo/framework/widget-core/d';
import * as css from './styles/Profile.m.css';
export interface ProfileProperties {
username?: string;
}
export default class Profile extends WidgetBase<ProfileProperties> {
protected render() {
const { username } = this.properties;
return v('h1', { classes: [css.root] }, [`Welcome ${username || 'Stranger'}!`]);
}
}
The basic assertion follows:
tests/unit/widgets/Profile.ts
const { describe, it } = intern.getInterface('bdd');
import harness from '@dojo/framework/testing/harness';
import assertionTemplate from '@dojo/framework/testing/assertionTemplate';
import { w, v } from '@dojo/framework/widget-core/d';
import Profile from '../../../src/widgets/Profile';
import * as css from '../../../src/widgets/styles/Profile.m.css';
const profileAssertion = assertionTemplate(() =>
v('h1', { classes: [css.root], '~key': 'welcome' }, ['Welcome Stranger!'])
);
Write in the test:
tests/unit/widgets/Profile.ts
const profileAssertion = assertionTemplate(() =>
v('h1', { classes: [css.root], '~key': 'welcome' }, ['Welcome Stranger!'])
);
describe('Profile', () => {
it('default renders correctly', () => {
const h = harness(() => w(Profile, {}));
h.expect(profileAssertion);
});
});
it('default renders correctly', () => {
const h = harness(() => w(Profile, {}));
h.expect(profileAssertion);
});
Now we look for the Profile
parts passed username
after the property, how to test the output:
tests/unit/widgets/Profile.ts
describe('Profile', () => {
...
it('renders given username correctly', () => {
// update the expected result with a given username
const namedAssertion = profileAssertion.setChildren('~welcome', [
'Welcome Kel Varnsen!'
]);
const h = harness(() => w(Profile, { username: 'Kel Varnsen' }));
h.expect(namedAssertion);
});
});
Here, we use the baseAssertion setChildren()
api, and then we use a special ~
selector to locate the key value ~message
of the node. ~key
Property (tsx the template is assertion-key
) is a special attribute assertion template will be deleted when asserted, thus rendering the structure when the match will not be displayed. This feature allows you to modify the assertion template to enable simple selection nodes without the need to expand the actual components render functions. Once we get to the message
node, we can be set to a desired sub-node the number 5
and then h.expect
use the template generates. It should be noted that the assertion templates when setting up a new value always returns assertion template, which can ensure that you do not accidentally modify an existing template (may lead to other tests fail), and allows you based on the new template, incremental step by step Construction of a new template.
Assertion template has the following API:
insertBefore(selector: string, children: () => DNode[]): AssertionTemplateResult;
insertAfter(selector: string, children: () => DNode[]): AssertionTemplateResult;
insertSiblings(selector: string, children: () => DNode[], type?: 'before' | 'after'): AssertionTemplateResult;
append(selector: string, children: () => DNode[]): AssertionTemplateResult;
prepend(selector: string, children: () => DNode[]): AssertionTemplateResult;
replaceChildren(selector: string, children: () => DNode[]): AssertionTemplateResult;
setChildren(selector: string, children: () => DNode[], type?: 'prepend' | 'replace' | 'append'): AssertionTemplateResult;
setProperty(selector: string, property: string, value: any): AssertionTemplateResult;
setProperties(selector: string, value: any | PropertiesComparatorFunction): AssertionTemplateResult;
getChildren(selector: string): DNode[];
getProperty(selector: string, property: string): any;
getProperties(selector: string): any;
replace(selector: string, node: DNode): AssertionTemplateResult;
remove(selector: string): AssertionTemplateResult;
Mocking
You may have noticed that when testing components, we mainly test the properties that various modifications, the user interface is correct rendering. They do not contain real business logic, but you may want to test whether, for example, click the button calls the property method. This test method does not actually care about what has been done, only care about the correct call interface. In this case, you can use something like Sinon library.
src/widgets/Action.ts
import WidgetBase from '@dojo/framework/widget-core/WidgetBase';
import { v, w } from '@dojo/framework/widget-core/d';
import Button from '@dojo/widgets/button';
import * as css from './styles/Action.m.css';
export default class Action extends WidgetBase<{ fetchItems: () => void }> {
protected render() {
return v('div', { classes: [css.root] }, [w(Button, { onClick: this.handleClick, key: 'button' }, ['Fetch'])]);
}
private handleClick() {
this.properties.fetchItems();
}
}
You may want to test, when you click the button whether it will call this.properties.fetchItems
the method.
tests/unit/widgets/Action.ts
const { describe, it } = intern.getInterface('bdd');
import harness from '@dojo/framework/testing/harness';
import { w, v } from '@dojo/framework/widget-core/d';
import { stub } from 'sinon';
describe('Action', () => {
const fetchItems = stub();
it('can fetch data on button click', () => {
const h = harness(() => w(Home, { fetchItems }));
h.expect(() => v('div', { classes: [css.root] }, [w(Button, { onClick: () => {}, key: 'button' }, ['Fetch'])]));
h.trigger('@button', 'onClick');
assert.isTrue(fetchItems.calledOnce);
});
});
In this case, you can mock an Action member fetchItems
, the method attempts to obtain the data item. Then you can use @button
locate button and the trigger button onClick
event, and then check fetchItems
whether the method is called once.
For more mocking information, please read the Sinon document.
function test
And process load and execute the code unit testing different functional testing to load a page and test the application of interactive features in the browser.
If you want to check the contents of a page corresponding to the route, you can simplify the test by updating the links.
src/widgets/Menu.ts
import WidgetBase from '@dojo/framework/widget-core/WidgetBase';
import { w } from '@dojo/framework/widget-core/d';
import Link from '@dojo/framework/routing/ActiveLink';
import Toolbar from '@dojo/widgets/toolbar';
import * as css from './styles/Menu.m.css';
export default class Menu extends WidgetBase {
protected render() {
return w(Toolbar, { heading: 'My Dojo App!', collapseWidth: 600 }, [
w(
Link,
{
id: 'home', // add id attribute
to: 'home',
classes: [css.link],
activeClasses: [css.selected]
},
['Home']
),
w(
Link,
{
id: 'about', // add id attribute
to: 'about',
classes: [css.link],
activeClasses: [css.selected]
},
['About']
),
w(
Link,
{
id: 'profile', // add id attribute
to: 'profile',
classes: [css.link],
activeClasses: [css.selected]
},
['Profile']
)
]);
}
}
When using the application, you need to click the profile
link, and then navigate to the user welcome page. You can write a functional test to verify this behavior.
tests/functional/main.ts
const { describe, it } = intern.getInterface('bdd');
const { assert } = intern.getPlugin('chai');
describe('routing', () => {
it('profile page correctly loads', ({ remote }) => {
return (
remote
// loads the HTML file in local node server
.get('../../output/dev/index.html')
// find the id of the anchor tag
.findById('profile')
// click on the link
.click()
// end this action
.end()
// find the h1 tag
.findByTagName('h1')
// get the text in the h1 tag
.getVisibleText()
.then((text) => {
// verify the content of the h1 tag on the profile page
assert.equal(text, 'Welcome Dojo User!');
})
);
});
});
When you run a functional test, Dojo provides a page to interact with remote
objects. Because the page is loaded and interact with the page is an asynchronous operation, it must be returned in the test remote
object.
Functional test can be performed on the command line.
Command Line
npm run test:functional
This will HTML page is loaded into the remote instance of Chrome on your computer to test interactive features.
Functional testing is very useful, it can ensure that your browser, your program code to work properly as expected.
You can read more about Intern functional test content.