shouldly 0.2.4 shouldly: ^0.2.4 copied to clipboard
A simple, extensible BDD assertion library which focuses on giving great error messages when the assertion fails.
shouldly #
Shouldly
is an assertion framework which focuses on giving great error messages when the assertion fails while being simple and terse.
shouldly
allows you write more readable test assertions.
Features #
- Better test failure messages
- More readable test code
- Conjunction support (
and
) - Custom assertions
Better test failure messages #
To get more contextual information
// Non-Shouldly
Expected: <18>
Actual: <17>
// Shouldly
Subject int should be
`18`
but was
`17`
// more with Shouldly 😍
customer.age should be
`18`
but was
`17`
// Non-Shouldly
Expected: not null
Actual: <null>
// Shouldly
Subject should not be null
// Non-Shouldly
Expected: some element <4>
Actual: [1, 2, 3, 5]
// Non-Shouldly
myList
[1, 2, 3, 5]
should contain
4
but does not
Readability #
More readable test code as an English sentence.
// without shouldly
expect(calculator.currentValue, 1);
// shouldly
calculator.currentValue.should.be(1);
You can mix up with Expected or Actual 🤔. But with shouldly
there is no way to mix up.
// without shouldly
expect(playerCharacter.health, 100);
expect(100, playerCharacter.health);
// shouldly
playerCharacter.health.should.be(100);
Conjunctions #
This is a real English sentence, is it not?
13.should.beOdd().and.beGreaterOrEqualThan(13);
participants.should.contain('Andrew').and.not.contain('Bobby');
Custom matchers #
extension CustomNumAssertions on NumericAssertions {
NumericAssertions get beNegative {
if (subject >= 0) {
throw ShouldlyTestFailureError('Number\n should be negative');
}
return NumericAssertions(subject);
}
}
Or more exotic matchers
test('Custom matchers', () {
final bobby = Customer(
isMarried: true,
gender: Gender.male,
);
bobby.should.beMale.and.beMarried;
final kate = Customer(
isMarried: true,
gender: Gender.female,
);
kate.should.beMarried.and.not.beMale;
});
Getting started #
Simple add shouldly
dependency into your project.
Usage #
Booleans #
test('false should be `false`', () {
false.should.beFalse();
});
test('false should not be `true`', () {
false.should.not.beTrue();
});
Numbers #
test('Int should be type of `int`', () {
10.should.beOfType<int>();
10.should.beAssignableTo<num>();
});
Strings #
test('should not start with substring', () {
const str = 'Flutter';
str.should.not.startWith('A');
});
DateTimes #
// 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),
);
Iterables #
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);
});
Maps #
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);
});
Functions #
test('should throw exact type of exception', () {
throwExactException.should.throwException<CustomException>();
});
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),
),
);
});
Objects #
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>();
});
Enums #
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
Writing Custom Matchers #
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,
);
}
Contributing #
We accept the following contributions:
- Reporting issues
- Fixing bugs
- More tests
- More class integrations (Streams? Futures?)
- Improving documentation and comments