Introduction

Dojo provides a robust testing framework using @dojo/cli-test-intern. It allows you to efficiently test the output of your widgets and validate your expectations.

FeatureDescription
Minimal APISimple API for testing and asserting Dojo widget’s expected virtual DOM and behavior.
Unit testsUnit tests are tests run via node and browser to test isolated blocks of code.
Functional testsFunctional tests are run using Selenium in the browser and test the overall functionality of the software as a user would interact with it.
AssertionsAssertions allow you to build expected render functions to validate the output of your widgets.

Basic usage

Testing Dojo applications

  • Run a project’s test suite
  1. dojo test

Dojo uses @dojo/cli-test-intern for running unit and functional tests in your tests folder.

Running specific test suites

Dojo supports two types of testing approaches, unit and functional. Unit tests are tests run via node and the local Selenium tunnel and test isolated blocks of code. Functional tests are run using Selenium in the browser and test the overall functionality of the software as a user would interact with it. Running unit and functional tests against Selenium requires running the appropriate build using @dojo/cli-build-app.

  • Run a project’s unit test suite
  1. dojo test --unit --config local
  • Run a project’s functional test suite locally in a headless Chrome instance using Selenium.
  1. dojo test --functional --config local

Writing unit tests

src/widgets/Home.tsx

  1. import { create, tsx } from '@dojo/framework/core/vdom';
  2. import * as css from './Home.m.css';
  3. const factory = create();
  4. const Home = factory(function Home() {
  5. return <h1 classes={[css.root]}>Home Page</h1>;
  6. });
  7. export default Home;

tests/unit/widgets/Home.tsx

  1. const { describe, it } = intern.getInterface('bdd');
  2. import { tsx } from '@dojo/framework/core/vdom';
  3. import renderer, { assertion } from '@dojo/framework/testing/renderer';
  4. import Home from '../../../src/widgets/Home';
  5. import * as css from '../../../src/widgets/Home.m.css';
  6. const baseAssertion = assertion(() => <h1 classes={[css.root]}>Home Page</h1>);
  7. describe('Home', () => {
  8. it('default renders correctly', () => {
  9. const r = renderer(() => <Home />);
  10. r.expect(baseAssertion);
  11. });
  12. });

The renderer API allows you to verify that the output of a rendered widget is what you expect.

  • Does it render as expected?
  • Do event handlers work as expected?

Writing functional tests

Functional tests allow a UI page to be loaded and its code executed in a real browser to better test widget behavior.

Writing functional tests means specifying the interaction that a user would have with a page in terms of clicking on elements, then validating the resulting page content.

tests/functional/main.ts

  1. describe('routing', () => {
  2. it('profile page correctly loads', ({ remote }) => {
  3. return (
  4. remote
  5. // loads the HTML file in local node server
  6. .get('../../output/dev/index.html')
  7. // find the id of the anchor tag
  8. .findById('profile')
  9. // click on the link
  10. .click()
  11. // end this action
  12. .end()
  13. // find the h1 tag
  14. .findByTagName('h1')
  15. // get the text in the h1 tag
  16. .getVisibleText()
  17. .then((text) => {
  18. // verify the content of the h1 tag on the profile page
  19. assert.equal(text, 'Welcome Dojo User!');
  20. })
  21. );
  22. });
  23. });

Using assertions

Assertions provide a way to create a base assertion that allow parts of the expected output to vary between tests.

  • Given a widget that renders output differently based on property values:

src/widgets/Profile.tsx

  1. import { create, tsx } from '@dojo/framework/core/vdom';
  2. import * as css from './Profile.m.css';
  3. export interface ProfileProperties {
  4. username?: string;
  5. }
  6. const factory = create().properties<ProfileProperties>();
  7. const Profile = factory(function Profile({ properties }) {
  8. const { username } = properties();
  9. return <h1 classes={[css.root]}>{`Welcome ${username || 'Stranger'}!`}</h1>;
  10. });
  11. export default Profile;
  • Create an assertion using @dojo/framework/testing/renderer#assertion

tests/unit/widgets/Profile.tsx

  1. const { describe, it } = intern.getInterface('bdd');
  2. import { tsx } from '@dojo/framework/core/vdom';
  3. import renderer, { assertion } from '@dojo/framework/testing/renderer';
  4. import Profile from '../../../src/widgets/Profile';
  5. import * as css from '../../../src/widgets/Profile.m.css';
  6. // Create an assertion
  7. const profileAssertion = assertion(() => <h1 classes={[css.root]}>Welcome Stranger!</h1>);
  8. describe('Profile', () => {
  9. it('default renders correctly', () => {
  10. const r = renderer(() => <Profile />);
  11. // Test against my base assertion
  12. r.expect(profileAssertion);
  13. });
  14. });

Wrapped test nodes, created using @dojo/framework/testing/renderer#wrap need to be specified in the assertion’s expected output in place of the standard widget to interact with assertion’s API. Note: when using wrapped VNodes with v(), the .tag property needs to get used, for example v(WrappedDiv.tag, {} []).

tests/unit/widgets/Profile.tsx

  1. const { describe, it } = intern.getInterface('bdd');
  2. import { tsx } from '@dojo/framework/core/vdom';
  3. import renderer { wrap, assertion } from '@dojo/framework/testing/renderer';
  4. import Profile from '../../../src/widgets/Profile';
  5. import * as css from '../../../src/widgets/Profile.m.css';
  6. // Create a wrapped test node
  7. const WrappedHeader = wrap('h1');
  8. // Create an assertion
  9. const profileAssertion = assertion(() => (
  10. // Use the wrapped node in place of the normal node
  11. <WrappedHeader classes={[css.root]}>Welcome Stranger!</WrappedHeader>
  12. ));
  13. describe('Profile', () => {
  14. it('default renders correctly', () => {
  15. const r = renderer(() => <Profile />);
  16. // Test against my base assertion
  17. r.expect(profileAssertion);
  18. });
  19. it('renders given username correctly', () => {
  20. // update the expected result with a given username
  21. const namedAssertion = profileAssertion.setChildren(WrappedHeader, () => ['Welcome Kel Varnsen!']);
  22. const r = renderer(() => <Profile username="Kel Varnsen" />);
  23. r.expect(namedAssertion);
  24. });
  25. });

Using the setChildren method of an assertion with a wrapped testing node, WrappedHeader in this case, will return an assertion with the updated virtual DOM structure. This resulting assertion can then be used to test widget output.