Last active
July 29, 2019 19:37
-
-
Save Aidanvii7/a1b004a90c6d03e14b5c2d29acfeab46 to your computer and use it in GitHub Desktop.
A fluent answer builder for Mockito for Dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dart:collection'; | |
import 'package:mockito/mockito.dart'; | |
typedef ComputedAnswer<T> = T Function(); | |
class AnswerBuilder<T> implements _AnswerBuilderNode { | |
@override | |
final AnswerBuilder<T> _parent; | |
@override | |
final ComputedAnswer<T> _answer; | |
const AnswerBuilder() | |
:_parent = null, | |
_answer = null; | |
const AnswerBuilder._withParent(final AnswerBuilder<T> parent, final ComputedAnswer<T> answer) | |
: assert(parent != null && answer != null), | |
_parent = parent, | |
_answer = answer; | |
AnswerBuilder<T> thenAnswer(final T nextAnswer, {int times = 1}) { | |
assert(nextAnswer != null && times != null); | |
return _flattenedAnswers(() => nextAnswer, times); | |
} | |
AnswerBuilder<T> thenComputeAnswer(final ComputedAnswer<T> computedAnswer, {int times = 1}) { | |
assert(computedAnswer != null && times != null); | |
return _flattenedAnswers(computedAnswer, times); | |
} | |
AnswerBuilder<T> thenThrow(final Exception exception, {int times = 1}) { | |
assert(exception != null && times != null); | |
return _flattenedAnswers(() => throw exception, times); | |
} | |
CompleteAnswerBuilder<T> thenAnswerIndefinitely(final T nextAnswer) { | |
assert(nextAnswer != null); | |
return CompleteAnswerBuilder._(this, () => nextAnswer); | |
} | |
CompleteAnswerBuilder<T> thenComputeAnswerIndefinitely(final ComputedAnswer<T> computedAnswer) { | |
assert(computedAnswer != null); | |
return CompleteAnswerBuilder._(this, computedAnswer); | |
} | |
CompleteAnswerBuilder<T> thenThrowIndefinitely(final Exception exception) { | |
assert(exception != null); | |
return CompleteAnswerBuilder._(this, () => throw exception); | |
} | |
AnswerBuilder _flattenedAnswers(ComputedAnswer<T> nextAnswer, int times) { | |
return range(1, times).fold<AnswerBuilder<T>>(this, (node, _) { | |
return AnswerBuilder._withParent(node, nextAnswer); | |
}); | |
} | |
} | |
class CompleteAnswerBuilder<T> implements _AnswerBuilderNode { | |
@override | |
final AnswerBuilder<T> _parent; | |
@override | |
final ComputedAnswer<T> _answer; | |
CompleteAnswerBuilder._(final AnswerBuilder<T> parent, final ComputedAnswer<T> answer) | |
: assert(parent != null && answer != null), | |
_parent = parent, | |
_answer = answer; | |
Answering<T> build() { | |
List<ComputedAnswer<T>> answers = []; | |
_AnswerBuilderNode current = this; | |
while (current != null) { | |
answers.add(current._answer); | |
current = current._parent; | |
} | |
return _Answering<T>(answers..removeLast()); | |
} | |
} | |
abstract class _AnswerBuilderNode<T> { | |
AnswerBuilder<T> get _parent; | |
ComputedAnswer<T> get _answer; | |
} | |
class _Answering<T> { | |
Queue<ComputedAnswer<T>> _answers; | |
_Answering(final List<ComputedAnswer<T>> answers) | |
: assert(answers != null && answers.isNotEmpty), | |
_answers = Queue.from(answers); | |
T call(final Invocation ignored) { | |
if (_answers.length == 1) { | |
return _answers.last(); | |
} else { | |
return _answers.removeLast()(); | |
} | |
} | |
} | |
// util method, could be somewhere else | |
Iterable<int> range(int start, int end) sync* { | |
assert(start != null); | |
assert(end != null); | |
if (start == end) | |
yield start; | |
else if (start < end) { | |
for (int i = start; i <= end; i++) | |
yield i; | |
} else { | |
for (int i = end; i >= start; i--) | |
yield i; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dart:math'; | |
import 'package:flutter_test/flutter_test.dart'; | |
import 'package:mockito/mockito.dart'; | |
import 'answer_builder.dart'; | |
// This class isn't reliable for tests as it generates random values, so it needs mocked | |
class ValueGenerator { | |
final _random = Random(); | |
int get value => _random.nextInt(100); | |
} | |
// This is the class we want to test, but it has a dependency on ValueGenerator. | |
class ValueMultiplier { | |
final ValueGenerator _valueGenerator; | |
ValueMultiplier(ValueGenerator valueGenerator) | |
: assert(valueGenerator != null), | |
_valueGenerator = valueGenerator; | |
int get valueMultiplied => _valueGenerator.value * 2; | |
} | |
// Since we only want to test ValueMultiplier, lets define a mock version of ValueGenerator | |
class MockValueGenerator extends Mock implements ValueGenerator {} | |
main() { | |
// create the mock ValueGenerator and inject it into the class we want to test, ValueMultiplier | |
final mockValueGenerator = MockValueGenerator(); | |
final tested = ValueMultiplier(mockValueGenerator); | |
group("When ValueGenerator will generate values from 1 to 12, then exception, then repeat 12 indefinitely", () { | |
setUpAll(() { | |
when(mockValueGenerator.value).thenAnswer( | |
// Usage here (easier than managing a Queue in a test which might get ugly): | |
AnswerBuilder<int>() | |
.thenAnswer(1) | |
.thenAnswer(2) | |
.thenAnswer(3) | |
.thenAnswer(4) | |
.thenAnswer(5) | |
.thenAnswer(6) | |
.thenAnswer(7) | |
.thenAnswer(8) | |
.thenAnswer(9) | |
.thenAnswer(10) | |
.thenAnswer(11) | |
.thenThrow(const IntegerDivisionByZeroException()) | |
.thenAnswerIndefinitely(12) | |
.build() | |
); | |
}); | |
test("first answer is 2", () { | |
expect(tested.valueMultiplied, 2); | |
}); | |
test("second answer is 4", () { | |
expect(tested.valueMultiplied, 4); | |
}); | |
test("third answer is 6", () { | |
expect(tested.valueMultiplied, 6); | |
}); | |
test("fourth answer is 8", () { | |
expect(tested.valueMultiplied, 8); | |
}); | |
test("fifth answer is 10", () { | |
expect(tested.valueMultiplied, 10); | |
}); | |
test("sixth answer is 12", () { | |
expect(tested.valueMultiplied, 12); | |
}); | |
test("seventh answer is 14", () { | |
expect(tested.valueMultiplied, 14); | |
}); | |
test("eighth answer is 16", () { | |
expect(tested.valueMultiplied, 16); | |
}); | |
test("ninth answer is 18", () { | |
expect(tested.valueMultiplied, 18); | |
}); | |
test("tenth answer is 20", () { | |
expect(tested.valueMultiplied, 20); | |
}); | |
test("eleventh answer is 22", () { | |
expect(tested.valueMultiplied, 22); | |
}); | |
test("twelfth answer throws an exception", () { | |
expect(() => tested.valueMultiplied, throwsA(const IntegerDivisionByZeroException())); | |
}); | |
test("thirteenth answer is 24", () { | |
expect(tested.valueMultiplied, 24); | |
}); | |
test("subsequent answers are also 24", () { | |
//just test a few times.. | |
final test = () => expect(tested.valueMultiplied, 24); | |
test(); | |
test(); | |
test(); | |
test(); | |
test(); | |
test(); | |
}); | |
}); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:flutter_test/flutter_test.dart'; | |
import 'package:mockito/mockito.dart'; | |
import 'answer_builder.dart'; | |
main() { | |
group("When AnswerBuilder is created", () { | |
CompleteAnswerBuilder<int> answerBuilder; | |
setUpAll(() { | |
answerBuilder = const AnswerBuilder<int>() | |
.thenAnswer(1) | |
.thenAnswer(2, times: 2) | |
.thenComputeAnswer(() => 3) | |
.thenComputeAnswer(() => 4, times: 4) | |
.thenThrow(const FormatException("wrong format")) | |
.thenThrow(const IntegerDivisionByZeroException(), times: 2) | |
.thenAnswerIndefinitely(6); | |
}); | |
group("When Answering is built", () { | |
Answering<int> answering; | |
setUpAll(() { | |
answering = answerBuilder.build(); | |
}); | |
test("first answer is correct", () { | |
expect(answering(null), 1); | |
}); | |
test("second answer is correct", () { | |
expect(answering(null), 2); | |
}); | |
test("third answer is correct", () { | |
expect(answering(null), 2); | |
}); | |
test("fourth answer is correct", () { | |
expect(answering(null), 3); | |
}); | |
test("fifth answer is correct", () { | |
expect(answering(null), 4); | |
}); | |
test("sixth answer is correct", () { | |
expect(answering(null), 4); | |
}); | |
test("seventh answer is correct", () { | |
expect(answering(null), 4); | |
}); | |
test("eighth answer is correct", () { | |
expect(answering(null), 4); | |
}); | |
test("ninth answer is correct", () { | |
expect(() => answering(null), throwsA(const FormatException("wrong format"))); | |
}); | |
test("tenth answer is correct", () { | |
expect(() => answering(null), throwsA(const IntegerDivisionByZeroException())); | |
}); | |
test("eleventh answer is correct", () { | |
expect(() => answering(null), throwsA(const IntegerDivisionByZeroException())); | |
}); | |
}); | |
}); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment