TestCaseCombinator<T> class

Builder for test cases based on all possible combinations of the input arguments.

Very useful for the cases when you need test cases covering all possible input parameter combinations.

Example:

We need to test the following code:

enum Food {
  apple,
  banana,
  yoghurt,
}

class Monkey {
  final bool stomachFull;

  const Monkey({
    this.stomachFull = false,
  });

  bool shouldEat(Food food) {
    if (stomachFull) return false;
    return food == Food.banana;
  }
}

Specifically, we're interested in shouldEat functionality.

Based on the logic we can see in shouldEat, we can determine that if monkey is given some food, it will only eat banana, but it will reject even a banana if it is already full.

Given that, we can determine that one condition is boolean (stomachFull), that can take 2 values - true and false. Another condition is Food, which has 3 options - apple, banana, and yoghurt.

This gives us 6 combinations total that we have to check. From those combinations we only have 1 case when monkey will eat the food - if it's stomach is not full and it's given a banana.

This is a perfect example when writing all cases by hand is a lot of time and effort, complex readability, and a significant probability of introducing errors.

Instead, we can use TestCaseCombinator to make all the combinations for us:

final _monkeyTestCases = TestCaseCombinator<({bool stomachFull, Food food})>(
  (stomachFull: false, food: Food.apple),
  [
    ([true, false], (input, value) => (stomachFull: value, food: input.food)),
    (Food.values, (input, value) => (stomachFull: input.stomachFull, food: value)),
  ],
)
  ..successfulCase((stomachFull: false, food: Food.banana));

Now we can write our test:

void main() {
  group('Verify monkey eating food', () {
    for (var testCase in _monkeyTestCases.testCases) {
      test(testCase.description, () {
        final monkey = Monkey(stomachFull: testCase.input.stomachFull);
        expect(monkey.shouldEat(testCase.input.food), testCase.isSuccessful,
            reason: testCase.isSuccessful
                ? 'Monkey is expected to eat ${testCase.input.food.name} when it is ${testCase.input.stomachFull ? 'full' : 'hungry'}'
                : 'Monkey should not eat ${testCase.input.food.name} when it is ${testCase.input.stomachFull ? 'full' : 'hungry'}');
      });
    }
  });
}

The test goes over all testCases, created by TestCaseCombinator and sets up a test for each one, where each test case provides a record with a particular input, in our case a record containing the stomach flag with some food, a description of the test case itself (example: true | Food.apple), and isSuccessful flag, indicating if the test case is considered successful (from the test perspective).

The only thing we had to do is add the actual expectation to our test.

The test will be called with the following parameters:

stomachFull food isSuccessful
true Food.apple false
true Food.banana false
true Food.yoghurt false
false Food.apple false
false Food.banana true
false Food.yoghurt false

That provided a pretty comprehensive testing of shouldEat method, covering all possible cases.

Now we can be sure the monkey is always eating healthy!

Constructors

TestCaseCombinator(T initialValue, List<TestCaseCombinatorArgument<T>> arguments)
Creates the combinator with initialValue which will be used for taking all possible combinations of arguments.

Properties

hashCode int
The hash code for this object.
no setterinherited
runtimeType Type
A representation of the runtime type of the object.
no setterinherited
testCases Iterable<({String description, T input, bool isSuccessful})>
Provides all test cases where each test case provides the input, description, and isSuccessful flag, indicating if this test case is expected to be a success.
no setter

Methods

noSuchMethod(Invocation invocation) → dynamic
Invoked when a nonexistent method or property is accessed.
inherited
successfulCase(T testCase) → void
Provides a testCase which indicates a successful case (for the test).
successfulCaseIf(bool comparator(T)) → void
Runs a comparator against all testCases and adds matching cases as successful.
toString() String
A string representation of this object.
inherited

Operators

operator ==(Object other) bool
The equality operator.
inherited