expect_error

Last updated:

0 purchases

expect_error Image
expect_error Images
Add to Cart

Description:

expect error

The expect_error package is a testing library inspired by Typescript's
// @expect-error, designed to help package authors to test compilation errors.
Usage #
expect_error exposes a compiles matcher, which can be used within unit tests.
A simple example would be:
// test/my_test.dart
import 'package:expect_error/expect_error.dart';
import 'package:test/test.dart';

void main() async {
final library = await Library.parseFromStacktrace();

test('String is not assignable to int', () async {
await expectLater(library.withCode('''
// expect-error: INVALID_ASSIGNMENT
int value = "string";
'''), compiles);
});
}
copied to clipboard
This example tests that the code:
int value = "string";
copied to clipboard
emits the compilation error "INVALID_ASSIGNMENT".
FAQ: Why use a comment in the code block instead of a matcher? #
You may wonder why:
await expectLater(library.withCode('''
// expect-error: INVALID_ASSIGNMENT
int value = "string";
'''), compiles);
copied to clipboard
is preferrable to:
await expectLater(library.withCode('''
int value = "string";
'''), throwsCompilationError('INVALID_ASSIGNMENT'));
copied to clipboard
The reason why expect_error relies on a comment is because a comment
doesn't simply communicate what the error is, but also where that error is.
When using // expect-error: x, only the next line is allowed to emit the
compilation error.
As such, if we do:
await expectLater(library.withCode('''
void main() {
// expect-error: INVALID_ASSIGNMENT
print('a');
int value = "string";
}
'''), compiles);
copied to clipboard
Then this test will fail. Because while the code block indeed contains an
INVALID_ASSIGMENT error, the error isn't on the print but instead on
the int value = 'string'.
To fix out test, we would have to move the comment on the line before the error:
await expectLater(library.withCode('''
void main() {
print('a');
// expect-error: INVALID_ASSIGNMENT
int value = "string";
}
'''), compiles);
copied to clipboard
Specifying multiple error codes at the same time. #
It is possible for expect-error to specify multiple codes at once by separating
them with a ,:
await expectLater(library.withCode(r'''
String fn(int a) => '';

// expect-error: NOT_ENOUGH_POSITIONAL_ARGUMENTS, INVALID_ASSIGNMENT
int a = fn();
'''), compiles);
copied to clipboard
Importing files/packages in code blocks #
It is possible to use import directives to import dart code within code blocks:
await expectLater(library.withCode(r'''
import 'package:riverpod/riverpod.dart';

final provider = Provider<int>((ref) {
// expect-error: INVALID_ASSIGNMENT
return 'string';
});
'''), compiles);
copied to clipboard
The imports available within our code blocks are dependent on that library variable.
When we do:
void main() async {
final library = await Library.parseFromStacktrace();

test('...', () async {
await expectLater(library.withCode(...), compiles);
});
}
copied to clipboard
that library variable we created tells expect_error what imports
the tested code block can use.
In particular, Library.parseFromStacktrace() makes our tested code behave as if
it was defined in a separate file within the same folder our test file.
As such, the following code is valid too:
import 'package:expect_error/expect_error.dart';
import 'package:test/test.dart';

// our test file can use relative import to import another file
import 'relative.dart';

void main() async {
final library = await Library.parseFromStacktrace();

test('...', () async {
await expectLater(library.withCode('''
// our tested code can also import the same file
import 'relative.dart';
'''), compiles);
});
}
copied to clipboard
Flutter support #
Unfortunately, since expect_error is built on top of the analyzer
package, it means that a package cannot both depend on Flutter and expect_error at
the same time.
As such, to use expect_error to test compilation errors when interacting with
Flutter, a workaround is necessary.
The solution is to move your tests that depends on Flutter in a separate package
used only for test purpose, with no dependency on Flutter.
As example, consider a package my_package that depends on Flutter, which exposes a
MyWidget class:
// my_package/lib/my_widget.dart
import 'package:flutter/material.dart';

class MyWidget extends StatelessWidget {
const MyWidget({Key? key, required String parameter}) : super(key: key);

...
}
copied to clipboard
To test this MyWidget class, rather than adding our test
within the my_package/test folder, we could create a new Dart project
such that our folder architecture looks like:
my_package
puspec.yaml
lib
my_widget.dart
expect_error_test
pubspec.yaml
test
my_widget_test.dart
copied to clipboard
This expect_error_test app would depend on expect_error:
name: expect_error_test
---
dev_dependencies:
expect_error: ...
copied to clipboard
Then, within expect_error_test/test/my_widget_test, we could do:
void main() {
final flutterLibrary = await Library.custom(
packageName: 'my_package', // the name of the package that contains this code block
packageRoot: '..', // the path to the root of this package
path: 'test/my_test.dart', // where the codeblock is located within the package
);

await expectLater(flutterLibrary.withCode(r'''
import 'package:my_package/my_widget.dart';

void main() {
// expect-error: MISSING_REQUIRED_ARGUMENT
MyWidget();
}
'''), compiles);
}
copied to clipboard
Notice how rather than Library.parseFromStacktrace() we used Library.custom(...).
By doing so, rather than assuming that our code block is within the same
folder as our test, we were able to make it behave as if our code block was
within my_package.
This way, our tests using expect_error are correctly able to import Flutter code.
Sponsors #

License:

For personal and professional use. You cannot resell or redistribute these repositories in their original state.

Files In This Product:

Customer Reviews

There are no reviews.