Testing Library for Dart

Dart bindings for the JS dom-testing-library and react-testing-library packages, which provide simple and complete DOM/React testing utilities that encourage good testing practices.

Dart CI API Documentation


Introduction

The libraries in this package help you to test Dart UI components in a user-centric way.

The more your tests resemble the way your software is used, the more confidence they can give you.

To paint the full picture of how / why to use this library, please read the JS Testing Library Introduction, Guiding Principles and FAQs.

How Does It Work?

Rather than dealing with OverReact UiComponent instances, or React VDom ReactElements, your tests will work with actual DOM nodes. The utilities this library provides facilitate querying the DOM in the same way the user would. Finding form elements by their label text (just like a user would), finding links and buttons from their text (like a user would). It also exposes a recommended way to find elements by an HTML data-test-id attribute value as an "escape hatch" for elements where the text content and label do not make sense or is not practical.

This library encourages your Dart web applications to be more accessible and allows you to get your tests closer to using your components the way a user will, which allows your tests to give you more confidence that your application will work when a real user uses it.

Getting Started

Before using this library, you should familiarize yourself with Dart, Dart's test library, ReactJS and OverReact first.

1. Add the necessary dependencies to your project

pubspec.yaml
dependencies:
  # This is not required to use this library, but it is assumed
  # that you are testing OverReact UI components, so it is 
  # shown here for completeness.
  over_react: ^4.0.0

dev_dependencies:
  build_runner: ">=1.7.1 <3.0.0"
  build_test: ">=0.10.9 <3.0.0"
  build_web_compilers: ">=2.12.0 <4.0.0"
  react_testing_library: ^1.1.13
  test: ^1.14.4
  # This is not technically required, 
  # but makes the HTML portion of your test bootstrapping much easier!
  test_html_builder: ^2.0.0

2. Configure build.yaml to generate test outputs

build.yaml
targets:
  $default:
    builders:
      build_web_compilers|entrypoint:
        # These are globs for the entrypoints you want to compile.
        generate_for:
          - test/**.browser_test.dart

The configuration above is a minimum recommendation only. Read more about configuring a Dart build to learn more / ensure your configuration meets your needs.

3. Configure dart_test.yaml to run your tests

Create dart_test.yaml in the root of your project.

dart_test.yaml
platforms:
  - chrome

paths:
  - test/unit/

The configuration above is a minimum recommendation only. Read more about configuring Dart tests, or about Dart's test library to learn more / ensure your configuration meets your needs.

4. Add an HTML template for your tests that will load the necessary React / react-testing-library JS files

NOTE: The names of the files below are recommendations only, and are not essential to a functional setup.

We strongly recommend using the test_html_builder library to create a template that will be used to load each .dart.js test file.

test/unit/rtl_unit_test_template.html
<!DOCTYPE html>
<html lang="en">
  <head>
      <title>{{testName}}</title>
      <script src="packages/react/react.js"></script>
      <script src="packages/react/react_dom.js"></script>
      <script src="packages/react_testing_library/js/react-testing-library.js"></script>

      {{testScript}}
      <script src="packages/test/dart.js"></script>
  </head>
</html>

Use the template .html file above and follow the test_html_builder library instructions for wiring it up!

Adding / committing your own HTML file(s)

If for some reason you do not wish to use the test_html_builder library to generate the necessary .html file(s), you must create one for each analogous *_test.dart file in which you are using react_testing_library as shown below. Note that you will have to have one .html file for each .dart file containing your unit tests.

test/unit/some_unit_test.html
<!DOCTYPE html>
<html lang="en">
  <head>
      <title>Some Unit Test</title>
      <script src="packages/react/react.js"></script>
      <script src="packages/react/react_dom.js"></script>
      <script src="packages/react_testing_library/js/react-testing-library.js"></script>

      <link rel="x-dart-test" href="some_unit_test.dart">
      <script src="packages/test/dart.js"></script>
  </head>
</html>

5. Write test(s) for your component(s)!

Using the render function, you can now generate DOM using React components and query within that DOM to get the element(s) you want to test!

Then, you can use the Matchers from the matchers.dart entrypoint to perform assertions on that DOM, and the events and user events entrypoints or whatever to interact with the DOM.

For more in depth examples, see the migration guides.

lib/src/components/greeting.dart
import 'package:over_react/over_react.dart';

part 'greeting.over_react.g.dart';

mixin GreetingProps on UiProps {
  String heading;
  String subHeading;
  String getStartedRoute;
}

UiFactory<GreetingProps> Greeting = uiFunction(
  (props) {
    return (Dom.header()..role = 'group')(
      Dom.h1()(props.heading),
      Dom.h2()(props.subHeading),
      (Dom.a()..href = props.getStartedRoute)('Get started'),
    );
  },
  _$GreetingConfig, // ignore: undefined_identifier
);
test/unit/components/greeting_test.dart
import 'dart:html';

import 'package:test/test.dart';
import 'package:react_testing_library/react_testing_library.dart' as rtl;
import 'package:react_testing_library/matchers.dart';

import 'package:your_package/src/components/greeting.dart';

main() {
  group('Greeting', () {
    rtl.RenderResult view;
    tearDown(() {
      view = null;
    });
    
    group('renders the expected elements:', () {
      group('a <header>', () {
        HtmlElement headerElement;
        
        setUp(() {
          view = rtl.render((Greeting()
            ..heading = 'Hello There!'
            ..subHeading = 'Welcome to the unit testing party.'
            ..getStartedRoute = '/start/partying'
          )());
          
          headerElement = view.getByRole('group');
        });
        
        test('with a child <h1> with a text value equal to props.heading', () {
          final h1Element = rtl.within(headerElement).getByRole('heading', level: 1); 
          expect(h1Element, hasTextContent('Hello There!'));
        });
        
        test('with a child <h2> with a text value equal to props.subHeading', () {
          final h2Element = rtl.within(headerElement).getByRole('heading', level: 2); 
          expect(h2Element, hasTextContent('Welcome to the unit testing party.'));
        });
        
        test('with a child <a> with an href equal to props.getStartedRoute', () {
          final anchorElement = rtl.within(headerElement).getByRole('link'); 
          expect(anchorElement, hasAttribute('href', '/start/partying'));
        });
      });
    });
  });
}

Read more about how queries are scoped to both the view and return value of within()in the above example

Migration Guides

Dart / JS API Parity

The react_testing_library Dart package strives to maintain API parity with the analogous JS testing-library packages within reason. However, there are certain differences that are either unavoidable, or intentional for the purposes of building Dart APIs that are easy to use and reason about.

Read more about Dart / JS API Parity for the following API categories:

Libraries

Dom

rtl.dom.accessibility Dom
rtl.dom.async Dom
rtl.dom.configure Dom
rtl.dom.debugging Dom
rtl.dom.events Dom
rtl.dom.queries Dom

Matchers

matchers Matchers
A collection of Dart Matchers to use when unit testing React components using the react_testing_library. [...]

React

rtl.react React

UserActions

user_event UserActions
A library that provides more advanced simulation of browser interactions than the built-in fireEvent method. [...]