| // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | |
| // for details. All rights reserved. Use of this source code is governed by a | |
| // BSD-style license that can be found in the LICENSE file. | |
| import 'dart:async'; | |
| import 'package:test/test.dart'; | |
| import 'package:test_api/src/backend/declarer.dart'; | |
| import 'package:test_api/src/backend/group.dart'; | |
| import 'package:test_api/src/backend/invoker.dart'; | |
| import 'package:test_api/src/backend/suite.dart'; | |
| import 'package:test_api/src/backend/test.dart'; | |
| import '../utils.dart'; | |
| late Suite _suite; | |
| void main() { | |
| setUp(() { | |
| _suite = Suite(Group.root([]), suitePlatform, ignoreTimeouts: false); | |
| }); | |
| group('.test()', () { | |
| test('declares a test with a description and body', () async { | |
| var bodyRun = false; | |
| var tests = declare(() { | |
| test('description', () { | |
| bodyRun = true; | |
| }); | |
| }); | |
| expect(tests, hasLength(1)); | |
| expect(tests.single.name, equals('description')); | |
| await _runTest(tests[0] as Test); | |
| expect(bodyRun, isTrue); | |
| }); | |
| test('declares a test with an object as the description', () async { | |
| var tests = declare(() { | |
| test(Object, () {}); | |
| }); | |
| expect(tests.single.name, equals('Object')); | |
| }); | |
| test('declares multiple tests', () { | |
| var tests = declare(() { | |
| test('description 1', () {}); | |
| test('description 2', () {}); | |
| test('description 3', () {}); | |
| }); | |
| expect(tests, hasLength(3)); | |
| expect(tests[0].name, equals('description 1')); | |
| expect(tests[1].name, equals('description 2')); | |
| expect(tests[2].name, equals('description 3')); | |
| }); | |
| }); | |
| group('.setUp()', () { | |
| test('is run before all tests', () async { | |
| var setUpRun = false; | |
| var tests = declare(() { | |
| setUp(() => setUpRun = true); | |
| test( | |
| 'description 1', | |
| expectAsync0(() { | |
| expect(setUpRun, isTrue); | |
| setUpRun = false; | |
| }, max: 1), | |
| ); | |
| test( | |
| 'description 2', | |
| expectAsync0(() { | |
| expect(setUpRun, isTrue); | |
| setUpRun = false; | |
| }, max: 1), | |
| ); | |
| }); | |
| await _runTest(tests[0] as Test); | |
| await _runTest(tests[1] as Test); | |
| }); | |
| test('can return a Future', () { | |
| var setUpRun = false; | |
| var tests = declare(() { | |
| setUp(() { | |
| return Future(() => setUpRun = true); | |
| }); | |
| test( | |
| 'description', | |
| expectAsync0(() { | |
| expect(setUpRun, isTrue); | |
| }, max: 1), | |
| ); | |
| }); | |
| return _runTest(tests.single as Test); | |
| }); | |
| test('runs in call order within a group', () async { | |
| var firstSetUpRun = false; | |
| var secondSetUpRun = false; | |
| var thirdSetUpRun = false; | |
| var tests = declare(() { | |
| setUp( | |
| expectAsync0(() async { | |
| expect(secondSetUpRun, isFalse); | |
| expect(thirdSetUpRun, isFalse); | |
| firstSetUpRun = true; | |
| }), | |
| ); | |
| setUp( | |
| expectAsync0(() async { | |
| expect(firstSetUpRun, isTrue); | |
| expect(thirdSetUpRun, isFalse); | |
| secondSetUpRun = true; | |
| }), | |
| ); | |
| setUp( | |
| expectAsync0(() async { | |
| expect(firstSetUpRun, isTrue); | |
| expect(secondSetUpRun, isTrue); | |
| thirdSetUpRun = true; | |
| }), | |
| ); | |
| test( | |
| 'description', | |
| expectAsync0(() { | |
| expect(firstSetUpRun, isTrue); | |
| expect(secondSetUpRun, isTrue); | |
| expect(thirdSetUpRun, isTrue); | |
| }), | |
| ); | |
| }); | |
| await _runTest(tests.single as Test); | |
| }); | |
| }); | |
| group('.tearDown()', () { | |
| test('is run after all tests', () async { | |
| late bool tearDownRun; | |
| var tests = declare(() { | |
| setUp(() => tearDownRun = false); | |
| tearDown(() => tearDownRun = true); | |
| test( | |
| 'description 1', | |
| expectAsync0(() { | |
| expect(tearDownRun, isFalse); | |
| }, max: 1), | |
| ); | |
| test( | |
| 'description 2', | |
| expectAsync0(() { | |
| expect(tearDownRun, isFalse); | |
| }, max: 1), | |
| ); | |
| }); | |
| await _runTest(tests[0] as Test); | |
| expect(tearDownRun, isTrue); | |
| await _runTest(tests[1] as Test); | |
| expect(tearDownRun, isTrue); | |
| }); | |
| test('is run after an out-of-band failure', () async { | |
| late bool tearDownRun; | |
| var tests = declare(() { | |
| setUp(() => tearDownRun = false); | |
| tearDown(() => tearDownRun = true); | |
| test( | |
| 'description 1', | |
| expectAsync0(() { | |
| Invoker.current!.addOutstandingCallback(); | |
| Future( | |
| () => throw TestFailure('oh no'), | |
| ).whenComplete(Invoker.current!.removeOutstandingCallback); | |
| }, max: 1), | |
| ); | |
| }); | |
| await _runTest(tests.single as Test, shouldFail: true); | |
| expect(tearDownRun, isTrue); | |
| }); | |
| test('can return a Future', () async { | |
| var tearDownRun = false; | |
| var tests = declare(() { | |
| tearDown(() { | |
| return Future(() => tearDownRun = true); | |
| }); | |
| test( | |
| 'description', | |
| expectAsync0(() { | |
| expect(tearDownRun, isFalse); | |
| }, max: 1), | |
| ); | |
| }); | |
| await _runTest(tests.single as Test); | |
| expect(tearDownRun, isTrue); | |
| }); | |
| test("isn't run until there are no outstanding callbacks", () async { | |
| var outstandingCallbackRemoved = false; | |
| var outstandingCallbackRemovedBeforeTeardown = false; | |
| var tests = declare(() { | |
| tearDown(() { | |
| outstandingCallbackRemovedBeforeTeardown = outstandingCallbackRemoved; | |
| }); | |
| test('description', () { | |
| Invoker.current!.addOutstandingCallback(); | |
| pumpEventQueue().then((_) { | |
| outstandingCallbackRemoved = true; | |
| Invoker.current!.removeOutstandingCallback(); | |
| }); | |
| }); | |
| }); | |
| await _runTest(tests.single as Test); | |
| expect(outstandingCallbackRemovedBeforeTeardown, isTrue); | |
| }); | |
| test( | |
| "isn't run until test body completes after out-of-band error", | |
| () async { | |
| var hasTestFinished = false; | |
| var hasTestFinishedBeforeTeardown = false; | |
| var tests = declare(() { | |
| tearDown(() { | |
| hasTestFinishedBeforeTeardown = hasTestFinished; | |
| }); | |
| test('description', () { | |
| Future<Never>.error('oh no'); | |
| return pumpEventQueue().then((_) { | |
| hasTestFinished = true; | |
| }); | |
| }); | |
| }); | |
| await _runTest(tests.single as Test, shouldFail: true); | |
| expect(hasTestFinishedBeforeTeardown, isTrue); | |
| }, | |
| ); | |
| test("doesn't complete until there are no outstanding callbacks", () async { | |
| var outstandingCallbackRemoved = false; | |
| var tests = declare(() { | |
| tearDown(() { | |
| Invoker.current!.addOutstandingCallback(); | |
| pumpEventQueue().then((_) { | |
| outstandingCallbackRemoved = true; | |
| Invoker.current!.removeOutstandingCallback(); | |
| }); | |
| }); | |
| test('description', () {}); | |
| }); | |
| await _runTest(tests.single as Test); | |
| expect(outstandingCallbackRemoved, isTrue); | |
| }); | |
| test('runs in reverse call order within a group', () async { | |
| var firstTearDownRun = false; | |
| var secondTearDownRun = false; | |
| var thirdTearDownRun = false; | |
| var tests = declare(() { | |
| tearDown( | |
| expectAsync0(() async { | |
| expect(secondTearDownRun, isTrue); | |
| expect(thirdTearDownRun, isTrue); | |
| firstTearDownRun = true; | |
| }), | |
| ); | |
| tearDown( | |
| expectAsync0(() async { | |
| expect(firstTearDownRun, isFalse); | |
| expect(thirdTearDownRun, isTrue); | |
| secondTearDownRun = true; | |
| }), | |
| ); | |
| tearDown( | |
| expectAsync0(() async { | |
| expect(firstTearDownRun, isFalse); | |
| expect(secondTearDownRun, isFalse); | |
| thirdTearDownRun = true; | |
| }), | |
| ); | |
| test( | |
| 'description', | |
| expectAsync0(() { | |
| expect(firstTearDownRun, isFalse); | |
| expect(secondTearDownRun, isFalse); | |
| expect(thirdTearDownRun, isFalse); | |
| }, max: 1), | |
| ); | |
| }); | |
| await _runTest(tests.single as Test); | |
| }); | |
| test('runs further tearDowns in a group even if one fails', () async { | |
| var tests = declare(() { | |
| tearDown(expectAsync0(() {})); | |
| tearDown(() async { | |
| throw 'error'; | |
| }); | |
| test('description', expectAsync0(() {})); | |
| }); | |
| await _runTest(tests.single as Test, shouldFail: true); | |
| }); | |
| test('runs in the same error zone as the test', () { | |
| return expectTestsPass(() { | |
| late Zone testBodyZone; | |
| tearDown(() { | |
| final tearDownZone = Zone.current; | |
| expect( | |
| tearDownZone.inSameErrorZone(testBodyZone), | |
| isTrue, | |
| reason: | |
| 'The tear down callback is in a different error zone ' | |
| 'than the test body.', | |
| ); | |
| }); | |
| test('test', () { | |
| testBodyZone = Zone.current; | |
| }); | |
| }); | |
| }); | |
| }); | |
| group('in a group,', () { | |
| test("tests inherit the group's description", () { | |
| var entries = declare(() { | |
| group('group', () { | |
| test('description', () {}); | |
| }); | |
| }); | |
| expect(entries, hasLength(1)); | |
| var testGroup = entries.single as Group; | |
| expect(testGroup.name, equals('group')); | |
| expect(testGroup.entries, hasLength(1)); | |
| expect(testGroup.entries.single, const TypeMatcher<Test>()); | |
| expect(testGroup.entries.single.name, 'group description'); | |
| }); | |
| test("tests inherit the group's description when it's not a string", () { | |
| var entries = declare(() { | |
| group(Object, () { | |
| test('description', () {}); | |
| }); | |
| }); | |
| expect(entries, hasLength(1)); | |
| var testGroup = entries.single as Group; | |
| expect(testGroup.name, equals('Object')); | |
| expect(testGroup.entries, hasLength(1)); | |
| expect(testGroup.entries.single, const TypeMatcher<Test>()); | |
| expect(testGroup.entries.single.name, 'Object description'); | |
| }); | |
| test("a test's timeout factor is applied to the group's", () { | |
| var entries = declare(() { | |
| group('group', () { | |
| test('test', () {}, timeout: const Timeout.factor(3)); | |
| }, timeout: const Timeout.factor(2)); | |
| }); | |
| expect(entries, hasLength(1)); | |
| var testGroup = entries.single as Group; | |
| expect(testGroup.metadata.timeout.scaleFactor, equals(2)); | |
| expect(testGroup.entries, hasLength(1)); | |
| expect(testGroup.entries.single, const TypeMatcher<Test>()); | |
| expect(testGroup.entries.single.metadata.timeout.scaleFactor, equals(6)); | |
| }); | |
| test("a test's timeout factor is applied to the group's duration", () { | |
| var entries = declare(() { | |
| group('group', () { | |
| test('test', () {}, timeout: const Timeout.factor(2)); | |
| }, timeout: const Timeout(Duration(seconds: 10))); | |
| }); | |
| expect(entries, hasLength(1)); | |
| var testGroup = entries.single as Group; | |
| expect( | |
| testGroup.metadata.timeout.duration, | |
| equals(const Duration(seconds: 10)), | |
| ); | |
| expect(testGroup.entries, hasLength(1)); | |
| expect(testGroup.entries.single, const TypeMatcher<Test>()); | |
| expect( | |
| testGroup.entries.single.metadata.timeout.duration, | |
| equals(const Duration(seconds: 20)), | |
| ); | |
| }); | |
| test("a test's timeout duration is applied over the group's", () { | |
| var entries = declare(() { | |
| group('group', () { | |
| test('test', () {}, timeout: const Timeout(Duration(seconds: 15))); | |
| }, timeout: const Timeout(Duration(seconds: 10))); | |
| }); | |
| expect(entries, hasLength(1)); | |
| var testGroup = entries.single as Group; | |
| expect( | |
| testGroup.metadata.timeout.duration, | |
| equals(const Duration(seconds: 10)), | |
| ); | |
| expect(testGroup.entries, hasLength(1)); | |
| expect(testGroup.entries.single, const TypeMatcher<Test>()); | |
| expect( | |
| testGroup.entries.single.metadata.timeout.duration, | |
| equals(const Duration(seconds: 15)), | |
| ); | |
| }); | |
| test('disallows asynchronous groups', () async { | |
| declare(() { | |
| expect(() => group('group', () async {}), throwsArgumentError); | |
| }); | |
| }); | |
| group('.setUp()', () { | |
| test('is scoped to the group', () async { | |
| var setUpRun = false; | |
| var entries = declare(() { | |
| group('group', () { | |
| setUp(() => setUpRun = true); | |
| test( | |
| 'description 1', | |
| expectAsync0(() { | |
| expect(setUpRun, isTrue); | |
| setUpRun = false; | |
| }, max: 1), | |
| ); | |
| }); | |
| test( | |
| 'description 2', | |
| expectAsync0(() { | |
| expect(setUpRun, isFalse); | |
| setUpRun = false; | |
| }, max: 1), | |
| ); | |
| }); | |
| await _runTest((entries[0] as Group).entries.single as Test); | |
| await _runTest(entries[1] as Test); | |
| }); | |
| test('runs from the outside in', () { | |
| var outerSetUpRun = false; | |
| var middleSetUpRun = false; | |
| var innerSetUpRun = false; | |
| var entries = declare(() { | |
| setUp( | |
| expectAsync0(() { | |
| expect(middleSetUpRun, isFalse); | |
| expect(innerSetUpRun, isFalse); | |
| outerSetUpRun = true; | |
| }, max: 1), | |
| ); | |
| group('middle', () { | |
| setUp( | |
| expectAsync0(() { | |
| expect(outerSetUpRun, isTrue); | |
| expect(innerSetUpRun, isFalse); | |
| middleSetUpRun = true; | |
| }, max: 1), | |
| ); | |
| group('inner', () { | |
| setUp( | |
| expectAsync0(() { | |
| expect(outerSetUpRun, isTrue); | |
| expect(middleSetUpRun, isTrue); | |
| innerSetUpRun = true; | |
| }, max: 1), | |
| ); | |
| test( | |
| 'description', | |
| expectAsync0(() { | |
| expect(outerSetUpRun, isTrue); | |
| expect(middleSetUpRun, isTrue); | |
| expect(innerSetUpRun, isTrue); | |
| }, max: 1), | |
| ); | |
| }); | |
| }); | |
| }); | |
| var middleGroup = entries.single as Group; | |
| var innerGroup = middleGroup.entries.single as Group; | |
| return _runTest(innerGroup.entries.single as Test); | |
| }); | |
| test('handles Futures when chained', () { | |
| var outerSetUpRun = false; | |
| var innerSetUpRun = false; | |
| var entries = declare(() { | |
| setUp( | |
| expectAsync0(() { | |
| expect(innerSetUpRun, isFalse); | |
| return Future(() => outerSetUpRun = true); | |
| }, max: 1), | |
| ); | |
| group('inner', () { | |
| setUp( | |
| expectAsync0(() { | |
| expect(outerSetUpRun, isTrue); | |
| return Future(() => innerSetUpRun = true); | |
| }, max: 1), | |
| ); | |
| test( | |
| 'description', | |
| expectAsync0(() { | |
| expect(outerSetUpRun, isTrue); | |
| expect(innerSetUpRun, isTrue); | |
| }, max: 1), | |
| ); | |
| }); | |
| }); | |
| var innerGroup = entries.single as Group; | |
| return _runTest(innerGroup.entries.single as Test); | |
| }); | |
| test("inherits group's tags", () { | |
| var tests = declare(() { | |
| group('outer', () { | |
| group('inner', () { | |
| test('with tags', () {}, tags: 'd'); | |
| }, tags: ['b', 'c']); | |
| }, tags: 'a'); | |
| }); | |
| var outerGroup = tests.single as Group; | |
| var innerGroup = outerGroup.entries.single as Group; | |
| var testWithTags = innerGroup.entries.single; | |
| expect(outerGroup.metadata.tags, unorderedEquals(['a'])); | |
| expect(innerGroup.metadata.tags, unorderedEquals(['a', 'b', 'c'])); | |
| expect( | |
| testWithTags.metadata.tags, | |
| unorderedEquals(['a', 'b', 'c', 'd']), | |
| ); | |
| }); | |
| test('throws on invalid tags', () { | |
| expect(() { | |
| declare(() { | |
| group('a', () {}, tags: 1); | |
| }); | |
| }, throwsArgumentError); | |
| }); | |
| }); | |
| group('.tearDown()', () { | |
| test('is scoped to the group', () async { | |
| late bool tearDownRun; | |
| var entries = declare(() { | |
| setUp(() => tearDownRun = false); | |
| group('group', () { | |
| tearDown(() => tearDownRun = true); | |
| test( | |
| 'description 1', | |
| expectAsync0(() { | |
| expect(tearDownRun, isFalse); | |
| }, max: 1), | |
| ); | |
| }); | |
| test( | |
| 'description 2', | |
| expectAsync0(() { | |
| expect(tearDownRun, isFalse); | |
| }, max: 1), | |
| ); | |
| }); | |
| var testGroup = entries[0] as Group; | |
| await _runTest(testGroup.entries.single as Test); | |
| expect(tearDownRun, isTrue); | |
| await _runTest(entries[1] as Test); | |
| expect(tearDownRun, isFalse); | |
| }); | |
| test('runs from the inside out', () async { | |
| var innerTearDownRun = false; | |
| var middleTearDownRun = false; | |
| var outerTearDownRun = false; | |
| var entries = declare(() { | |
| tearDown( | |
| expectAsync0(() { | |
| expect(innerTearDownRun, isTrue); | |
| expect(middleTearDownRun, isTrue); | |
| outerTearDownRun = true; | |
| }, max: 1), | |
| ); | |
| group('middle', () { | |
| tearDown( | |
| expectAsync0(() { | |
| expect(innerTearDownRun, isTrue); | |
| expect(outerTearDownRun, isFalse); | |
| middleTearDownRun = true; | |
| }, max: 1), | |
| ); | |
| group('inner', () { | |
| tearDown( | |
| expectAsync0(() { | |
| expect(outerTearDownRun, isFalse); | |
| expect(middleTearDownRun, isFalse); | |
| innerTearDownRun = true; | |
| }, max: 1), | |
| ); | |
| test( | |
| 'description', | |
| expectAsync0(() { | |
| expect(outerTearDownRun, isFalse); | |
| expect(middleTearDownRun, isFalse); | |
| expect(innerTearDownRun, isFalse); | |
| }, max: 1), | |
| ); | |
| }); | |
| }); | |
| }); | |
| var middleGroup = entries.single as Group; | |
| var innerGroup = middleGroup.entries.single as Group; | |
| await _runTest(innerGroup.entries.single as Test); | |
| expect(innerTearDownRun, isTrue); | |
| expect(middleTearDownRun, isTrue); | |
| expect(outerTearDownRun, isTrue); | |
| }); | |
| test('handles Futures when chained', () async { | |
| var outerTearDownRun = false; | |
| var innerTearDownRun = false; | |
| var entries = declare(() { | |
| tearDown( | |
| expectAsync0(() { | |
| expect(innerTearDownRun, isTrue); | |
| return Future(() => outerTearDownRun = true); | |
| }, max: 1), | |
| ); | |
| group('inner', () { | |
| tearDown( | |
| expectAsync0(() { | |
| expect(outerTearDownRun, isFalse); | |
| return Future(() => innerTearDownRun = true); | |
| }, max: 1), | |
| ); | |
| test( | |
| 'description', | |
| expectAsync0(() { | |
| expect(outerTearDownRun, isFalse); | |
| expect(innerTearDownRun, isFalse); | |
| }, max: 1), | |
| ); | |
| }); | |
| }); | |
| var innerGroup = entries.single as Group; | |
| await _runTest(innerGroup.entries.single as Test); | |
| expect(innerTearDownRun, isTrue); | |
| expect(outerTearDownRun, isTrue); | |
| }); | |
| test('runs outer callbacks even when inner ones fail', () async { | |
| var outerTearDownRun = false; | |
| var entries = declare(() { | |
| tearDown(() { | |
| return Future(() => outerTearDownRun = true); | |
| }); | |
| group('inner', () { | |
| tearDown(() { | |
| throw 'inner error'; | |
| }); | |
| test( | |
| 'description', | |
| expectAsync0(() { | |
| expect(outerTearDownRun, isFalse); | |
| }, max: 1), | |
| ); | |
| }); | |
| }); | |
| var innerGroup = entries.single as Group; | |
| await _runTest(innerGroup.entries.single as Test, shouldFail: true); | |
| expect(outerTearDownRun, isTrue); | |
| }); | |
| }); | |
| }); | |
| group('duplicate names', () { | |
| test('can be enabled', () { | |
| expect( | |
| () => declare(() { | |
| test('a', expectAsync0(() {}, count: 0)); | |
| test('a', expectAsync0(() {}, count: 0)); | |
| }, allowDuplicateTestNames: false), | |
| throwsA( | |
| isA<DuplicateTestNameException>().having((e) => e.name, 'name', 'a'), | |
| ), | |
| ); | |
| }); | |
| test('are allowed by default', () { | |
| expect( | |
| declare(() { | |
| test('a', expectAsync0(() {}, count: 0)); | |
| test('a', expectAsync0(() {}, count: 0)); | |
| }).map((e) => e.name), | |
| equals(['a', 'a']), | |
| ); | |
| }); | |
| }); | |
| } | |
| /// Runs [test]. | |
| /// | |
| /// This automatically sets up an `onError` listener to ensure that the test | |
| /// doesn't throw any invisible exceptions. | |
| Future _runTest(Test test, {bool shouldFail = false}) { | |
| var liveTest = test.load(_suite); | |
| if (shouldFail) { | |
| liveTest.onError.listen(expectAsync1((_) {})); | |
| } else { | |
| liveTest.onError.listen((e) => registerException(e.error, e.stackTrace)); | |
| } | |
| return liveTest.run(); | |
| } |