shouldly
실패할 경우 훌륭한 오류 메시지를 제공하는 데 중점을 두고 있는 간단하고 확장 가능한 BDD 주장 라이브러리입니다.
AndrewPiterov/shouldly open-source repository details.
아래는 영문 원문 스냅샷입니다. 최신 내용은 GitHub에서 확인하세요.
shouldly is an assertion concept that focuses on
Inspired from Fluent Assertion, Shouldly, should.js
and)More readable test code as plain English sentence.
void main() {
const playerCharacter = PlayerCharacter(
'Arthur',
100,
['Axe', 'Sword', 'Staff of Wonder'],
);
test('equality', () {
// non shouldly
expect(playerCharacter.health, 100);
// 😍 shouldly
playerCharacter.health.should.be(100);
});
test('array contains', () {
// non shouldly
expect(playerCharacter.weapons, contains('Staff of Wonder'));
// 😍 shouldly
playerCharacter.weapons.should.contain('Staff of Wonder');
});
test('nullability of string', () {
// non shouldly
expect(playerCharacter.nickname, isNotNull);
// 😍 shouldly
playerCharacter.nickname.should.not.beNullOrEmpty();
});
}
To get more readable and contextual information.
| Non-Shouldly | Shouldly |
Expected: <10> Actual: <100> |
Expected playerCharacter.health
should be
10
but was
100
|
Expected: null Actual: 'Arthur' |
Expected nickname
should be null or empty
but was
`Arthur`
|
Expected: contains 'Staff of Wonderr' Actual: ['Axe', 'Sword', 'Staff of Wonder'] |
Expected playerCharacter.weapons
should contain
Staff of Wonder
but was actually
[Axe, Sword]
|
You can mix up with Expected or Actual 🤔. But with shouldly there is no way to mix up.
// without shouldly
expect(playerCharacter.health, 0);
expect(0, playerCharacter.health);
// shouldly
playerCharacter.health.should.be(0);
You could write such crazy assertions.
expect(2, isTrue);
expect(2, contains(2));
expect(2, startsWith('1'));
expect(2, isEmpty);
expect(2, throwsException);
Every single type of class has his own assertions. Easy to find required assertion method.
This is a real English sentence, is it not?
13.should.beOdd().and.beGreaterOrEqualThan(13);
participants.should.contain('Andrew').and.not.contain('Bobby');
satisfyAllConditions will show all errors at once, not only first fail assertion.
test('should satisfy all conditions', () {
Should.satisfyAllConditions([
() => playerCharacter.nickname.should.as('nickname').not.beNullOrEmpty(),
() => playerCharacter.weapons.should.as('weapons').contain('Staff of Wonder'),
() => playerCharacter.health.should.as('health').be(100),
]);
});
Example report can be:
Expected satisfy all conditions specified, but does not.
The following errors were found ...
------------- Error 1 -------------
Expected nickname
should not be null or empty
but was
''
------------- Error 2 -------------
Expected weapons
should contain
Staff of Wonder
but was actually
[Axe, Sword]
------------- Error 3 -------------
Expected health
should be
100
but was
99
------------------------------------
extension CustomNumAssertions on NumericAssertions {
NumericAssertions get beNegative {
if (subject >= 0) {
throw ShouldlyTestFailureError('Number\n should be negative');
}
return NumericAssertions(subject);
}
}
BaseAssertionsclass CustomerAssertions extends BaseAssertions<Customer, CustomerAssertions> {
// ...
CustomerAssertions get beMarried {
if (isReversed) {
if (subject!.isMarried) {
throw ShouldlyTestFailureError('Customer should not be married');
}
} else {
if (!subject!.isMarried) {
throw ShouldlyTestFailureError('Customer should be married');
}
}
return CustomerAssertions(subject);
}
CustomerAssertions get beMale {
if (isReversed) {
Execute.assertion
.forCondition(subject!.gender == Gender.male)
.failWith('$subjectLabel should not be male');
} else {
Execute.assertion
.forCondition(subject!.gender != Gender.male)
.failWith('$subjectLabel should be male');
}
Execute.assertion
.forCondition(subject!.gender != Gender.male)
.failWith('$subjectLabel should be male');
return CustomerAssertions(subject);
}
// ...
}
should property with type of your custom assertions classextension CustomerExtension on Customer {
CustomerAssertions get should => CustomerAssertions(this);
}
shouldly assertionstest('Custom matchers', () {
bobby.should.beMale.and.beMarried;
kate.should.beMarried.and.not.beMale;
});
To verify how long your function is executed
test('should complete in a duration', () async {
await Should.completeIn(
Duration(seconds: 1),
func: () => slowFunction(
Duration(milliseconds: 900),
),
);
});
test('should complete after a period of time', () async {
await Should.completeAfter(
const Duration(seconds: 3),
func: () => verySlowFunction(
const Duration(seconds: 4),
),
);
});
Simple add shouldly dependency into your project.
dev_dependencies:
shouldly: <latest>
Every single object has following assertion methods:
| Method | Example | Failure message |
|---|---|---|
| be | 1.should.be(2); |
Expected int should be 2 but was 1 |
| beOfType | 2.0.should.beOfType<double>(); |
|
| beAssignableTo | 3.should.beAssignableTo<int>(); |
|
| beNull | null.should.beNull(); |
|
| beOneOf | 5.should.beOneOf([1, 2, 5]); |
test('should be not null', () {
final obj = Object();
obj.should.not.beNull();
});
test('should be null', () {
const Object? obj = null;
obj.should.beNull();
});
test('should be type of `int`', () {
const obj = 1;
obj.should.beOfType<int>();
});
test('should be assignable to `num`', () {
const obj = 1;
obj.should.beAssignableTo<num>();
});
| Method | Example | Failure message |
|---|---|---|
| beTrue | false.should.beTrue(); |
Target boolean should be True but was False |
| beFalse | false.should.not.beFalse(); |
Target boolean should not be False but was False |
test('false should be `false`', () {
false.should.beFalse();
});
test('false should not be `true`', () {
false.should.not.beTrue();
});
| Method | Example | Failure message |
|---|---|---|
| bePositive | (-1).should.bePositive(); |
Target int should be positive but was negative as -1 |
| beNegative | 1.should.beNegative(); |
Target int should be negative but was positive as 1 |
| beZero | 10.should.beZero(); |
Target int should be 0 but was 10 |
| beOdd | 8.should.beOdd(); |
Target int should be odd but was even as 8 |
| beEven | 7.should.beEven(); |
Target int should be event but was odd as 7 |
| beGreaterThan | 1.should.beGreaterThan(2); |
Target int should be greater than 2 but does not |
| beAbove | 3.should.beAbove(2); |
|
| beLessThan | 3.should.beLessThan(4); |
|
| beBelow | 3.should.beBelow(4); |
|
| beGreaterOrEqualThan | 3.should.beGreaterOrEqualThan(3); |
|
| beLessOrEqualThan | 3.should.beLessOrEqualThan(3); |
|
| beWithin | 3.should.beWithin(1,5); |
|
| beCloseTo | pi.should.beCloseTo(3.14, delta: 0.01); |
|
| beTolerantOf | pi.should.beTolerantOf(3.14, tolerance: 1%); |
test('Int should be type of `int`', () {
2.should.beEven();
10.should.beGreaterThan(9);
9.99.should.not.beCloseTo(10.0, delta: 0.01);
});
| Method | Example | Failure message |
|---|---|---|
| startWith | 'Flutter'.should.startWith('f'); |
|
| endWith | 'Flutter'.should.endWith('a'); |
|
| haveLength | 'Flutter'.should.haveLength(10); |
|
| beNullOrEmpty | 'Flutter'.should.beNullOrEmpty(); |
|
| beNullOrWhiteSpace | 'Flutter'.should.beNullOrWhiteSpace(); |
|
| beBlank | 'Flutter'.should.beBlank(); |
|
| match | 'Flutter'.should.match('*a'); |
|
| contain | 'Flutter'.should.contain('a'); |
test('should not start with substring', () {
'Flutter'.should.not.startWith('A');
});
| Method | Example | Failure message |
|---|---|---|
| beCloseTo | DateTime.now().should.beCloseTo(Date(2025, 1, 1)); |
|
| beAfter | DateTime.now().should.beCloseTo(Date(2022, 1, 1)); |
|
| beBefore | DateTime.now().should.beBefore(Date(2222, 1, 1)); |
// before
DateTime(2021, 9, 9).should.beBefore(DateTime(2021, 9, 10));
DateTime(2021, 9, 9).should.not.beBefore(DateTime(2021, 9, 9));
// close to
DateTime(2021, 9, 9, 1, 1, 1, 2).should.beCloseTo(
DateTime(2021, 9, 9, 1, 1, 1, 3),
delta: Duration(milliseconds: 1),
);
| Method | Example | Failure message |
|---|---|---|
| haveCount | [1, 2, 3].should.haveCount(7); |
|
| contain | [1, 2, 3].should.contain(7); |
|
| containAll | [1, 2, 3].should.containAll([7, 8]); |
|
| every | [1, 2, 3].should.every((x) => x > 10); |
|
| any | [1, 2, 3].should.any((x) => x == 0); |
test('should contain', () {
[1, 200, 3].should.contain(200);
});
test('should not contain', () {
[1, 2, 4].should.not.contain(3);
});
test('with every element in collection is true for predicate', () {
[3, 5, 7, 9].should.every((item) => item < 10);
});
test('with some elements in collection is true for predicate', () {
[3, 5, 7, 9].should.any((item) => item > 8);
});
| Method | Example | Failure message |
|---|---|---|
| haveCount | {}.should.haveCount(7); |
|
| beEmpty | {}.should.beEmpty(); |
|
| containKey | {'name': 'Bobby'}.should.containKey('age'); |
|
| containKeys | {}.should.containKeys(['age', 'name']); |
|
| haveValueInKey | {}.should.haveValueInKey('name'); |
|
| containKeyWithValue | {}.should.containKeyWithValue('name', 'Bobby'); |
|
| contain | {}.should.haveValueInKey(<MapEntry>[]); |
final subject = {
'name': 'John',
'age': 18,
};
test('should contain key', () {
subject.should.containKey('name');
});
test('should contain key with exact value', () {
subject.should.containKeyWithValue('age', 18);
});
| Method | Example | Failure message |
|---|---|---|
| throwException | Should.throwException(() => myFunc()); |
|
| notThrowException | Should.notThrowException(() => myFunc()); |
|
| throwError | Should.throwError(() => myFunc()); |
|
| notThrowError | Should.notThrowError(() => myFunc()); |
|
| completeIn | Should.completeIn(Duration(seconds: 1), () => myFunc()); |
Should.throwException(() => someMethodWitchThrowException(params:));
Should.throwError<ExactError>(() => someMethodWitchThrowExactError(params:));
test('async function should throw exception', () async {
await Should.throwAsync(() {
Future.delayed(Duration(milliseconds: 100));
throw Exception('test');
});
});
test('async function should throw exact exception', () async {
await Should.throwAsync<CustomException>(() {
Future.delayed(Duration(milliseconds: 100));
throw CustomException('custom exception test');
});
});
test('should complete in a duration', () async {
await Should.completeIn(
Duration(seconds: 1),
func: () => slowFunction(
Duration(milliseconds: 900),
),
);
});
test('should not be equal', () {
seasons.spring.should.not.be(seasons.winter);
});
test('should not be type of', () {
seasons.spring.should.not.beOfType<level>();
});
test('should be assignable to `Enum`', () {
seasons.spring.should.beAssignableTo<Enum>();
});
More examples here
extension CustomerExtension on Customer {
CustomerAssertions get should => CustomerAssertions(this);
}
class CustomerAssertions extends BaseAssertions<Customer, CustomerAssertions> {
CustomerAssertions(
Customer? subject, {
bool isReversed = false,
String? subjectLabel,
}) : super(subject, isReversed: isReversed, subjectLabel: subjectLabel);
CustomerAssertions get beMarried {
if (isReversed) {
if (subject!.isMarried) {
throw ShouldlyTestFailure('Customer should not be married');
}
} else {
if (!subject!.isMarried) {
throw ShouldlyTestFailure('Customer should be married');
}
}
return CustomerAssertions(subject);
}
CustomerAssertions get beMale {
if (isReversed) {
if (subject!.gender == Gender.male) {
throw ShouldlyTestFailure('Customer should be female');
}
} else {
if (subject!.gender != Gender.male) {
throw ShouldlyTestFailure('Customer should be male');
}
}
return CustomerAssertions(subject);
}
@override
CustomerAssertions copy(
Customer? subject, {
bool isReversed = false,
String? subjectLabel,
}) =>
CustomerAssertions(
subject,
isReversed: isReversed,
subjectLabel: subjectLabel,
);
}
You can improve the readability of the rest of your test code with given_when_then_unit_test, which enhances the test report readability as well.
Please see the Changelog page to know what's recently changed.
Feel free to contribute to this project.
If you find a bug or want a feature, but don't know how to fix/implement it, please fill an issue.
If you fixed a bug or implemented a new feature, please send a pull request.
We accept the following contributions:
의존성 목록 4 개
^1.15.0^0.0.10^1.8.2^1.20.1사용 목적에 따라 검색하고 개발 활동을 비교한 뒤 원본 저장소에서 요구 사항을 확인하세요.
“A repository is the most basic element of GitHub.”
GitHub Docs — About repositories ↗