Skip to content

Instantly share code, notes, and snippets.

@Aidanvii7
Last active July 29, 2019 19:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Aidanvii7/a1b004a90c6d03e14b5c2d29acfeab46 to your computer and use it in GitHub Desktop.
Save Aidanvii7/a1b004a90c6d03e14b5c2d29acfeab46 to your computer and use it in GitHub Desktop.
A fluent answer builder for Mockito for Dart
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;
}
}
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();
});
});
}
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