kiri-check Help

Arbitraries

Arbitraries are components that generate data and perform shrinking, which are especially crucial elements in property-based testing.

In this document, the values handled by arbitraries are referred to as follows:

  • Example: A value generated by an arbitrary

  • Counterexample: A value that causes the test to fail

  • Shrunk: A value generated during the shrinking process

  • Falsifying example: The minimal value discovered through shrinking

Basic data types

Arbitraries that generate values of basic data types.

null_

Arbitrary<Null> null_()

Generates null.

Examples from this arbitrary will never be shrunk.

boolean

Arbitrary<bool> boolean()

Generates either true or false.

Examples from this arbitrary will never be shrunk.

integer

Arbitrary<int> integer({ int? min, int? max, })

Generates integers between min and max. If no range is specified, it generates integers from the minimum to the maximum value of int.

Examples from this arbitrary will be shrunk towards zero. Negative numbers are not shrunk.

float

Arbitrary<double> float({ double? min, double? max, bool? minExcluded, bool? maxExcluded, bool? nan, bool? infinity, })

Generates floating-point numbers between min and max. If no range is specified, it generates floating-point numbers from the minimum to the maximum value of double.

If minExcluded and maxExcluded are true, the generated values will exclude the minimum and maximum values.

If nan is true, it also generates NaN.

If infinity is true, it also generates infinity.

Examples from this arbitrary will be shrunk towards zero. Negative numbers are not shrunk.

binary

Arbitrary<List<int>> binary({ int? min, int? max, })

Generates binary data (a list of integers ranging from 0 to 255) of lengths between min and max. If no range is specified, it generates binary data of lengths from 0 to 100.

Examples from this arbitrary will be shrunk towards the empty list.

Strings

runes

Arbitrary<Runes> runes({ int? minLength, int? maxLength, CharacterSet? characterSet, })

Generates runes with lengths ranging from min to max. If no range is specified, it generates runes with lengths from 0 to 100.

The character types for the generated runes can be specified using characterSet.

Examples from this arbitrary will be shrunk towards empty runes.

string

Arbitrary<String> string({ int? minLength, int? maxLength, CharacterSet? characterSet, })

Generates strings with lengths ranging from min to max. If no range is specified, it generates strings with lengths from 0 to 100.

The character types for the generated strings can be specified using characterSet.

Examples from this arbitrary will be shrunk towards the empty string.

CharacterSet

CharacterSet is a set of Unicode characters used to specify the characters to be generated in runes and string.

CharacterSet allows you to specify:

  • Character encodings (ASCII, UTF-8, UTF-16)

  • Specific characters or code points

  • Unicode categories

For further details, refer to the API reference.

Collections

Arbitraries that generate lists, maps, and sets.

list

Arbitrary<List<T>> list<T>( Arbitrary<T> element, { int? minLength, int? maxLength, bool? unique, bool Function(T, T)? uniqueBy, })

Generates a list containing elements produced by the element arbitrary. The length of the list can be specified within the range from minLength to maxLength. If no range is specified, lists of lengths from 0 to 10 are generated.

If unique is true, the elements of the list will not have duplicates. The comparison between elements is performed using ==. Removed duplicates are not replenished, so the list's length may fall below minLength.

If uniqueBy is specified, the elements of the list are compared using the function provided by uniqueBy.

Examples from this arbitrary will be shrunk towards the empty list.

map

Arbitrary<Map<K, V>> map<K, V>( Arbitrary<K> key, Arbitrary<V> value, { int? minLength, int? maxLength, })

Generates a map with keys and values produced by the key and value arbitraries, respectively. The length of the map can be specified within the range from minLength to maxLength. If no range is specified, maps of lengths from 0 to 10 are generated.

Examples from this arbitrary will be shrunk towards the empty map.

set

Arbitrary<Set<T>> set<T>( Arbitrary<T> element, { int? minLength, int? maxLength, })

Generates a set containing elements produced by the element arbitrary. The length of the set can be specified within the range from minLength to maxLength. If no range is specified, sets of lengths from 0 to 10 are generated.

Examples from this arbitrary will be shrunk towards the empty set.

Dates and times

Arbitraries that generate date and time values. There are no arbitraries that generate only time; if only time is needed, use dateTime and ignore the date component.

dateTime

Arbitrary<tz.TZDateTime> dateTime({ DateTime? min, DateTime? max, String? location, })

Generates datetime values between min and max. If no range is specified, it generates datetime values from January 1, 0000, 00:00:00 to December 31, 9999, 23:59:59.

The generated values are of the TZDateTime type from the package:timezone. Dart's DateTime does not hold timezone information other than local time and UTC, hence the use of package:timezone. TZDateTime is compatible with DateTime, so it can be used in the same way.

If location is specified, datetime is generated in that timezone. If not specified, local time is generated.

The DateTime specified in min and max purely uses the time component, and the timezone of the DateTime is ignored.

Examples from this arbitrary will be shrunk towards midnight on January 1st, 2000, local time.

nominalDateTime

Arbitrary<NominalDateTime> nominalDateTime({ DateTime? min, DateTime? max, String? location, bool? imaginary, })

Generates datetime values between min and max. If no range is specified, it generates datetime values from January 1, 0000, 00:00:00 to December 31, 9999, 23:59:59. The basic behavior is the same as dateTime.

The differences from dateTime are as follows:

  • If imaginary is set to true, it can generate imaginary datetime (fictional dates).

  • Returns NominalDateTime instead of DateTime.

Imaginary dates refer to dates that do not actually exist, such as February 29th in a non-leap year or the 31st of a month that does not have 31 days. Currently, the generation of non-existent times occurring at the start and end times of daylight saving time is not implemented.

NominalDateTime is an object that holds the date and time exactly as numerically represented. Unlike DateTime, it does not validate for authenticity, so it can hold imaginary dates as they are. It can be converted to TZDateTime using toTZDateTime, but imaginary dates are converted to real time. The conversion method depends on TZDateTime.

Constant values

Arbitraries that generate constant values.

constant

Arbitrary<T> constant<T>(T value)

Generates only the value. Be cautious if passing mutable objects as value is not copied.

Examples from this arbitrary will never be shrunk.

constantFrom

Arbitrary<T> constantFrom<T>(List<T> values)

Randomly selects and generates one item from values. Be cautious as values are not copied, which is important if mutable objects are used.

Examples from this arbitrary will never be shrunk.

Composition

Arbitraries that can combine multiple arbitraries.

combine

combine2<(E1, E2)>( Arbitrary<E1> a1, Arbitrary<E2> a2, ) combine3<(E1, E2, E3)>( Arbitrary<E1> a1, Arbitrary<E2> a2, Arbitrary<E3> a3, ) combine4<(E1, E2, E3, E4)>( Arbitrary<E1> a1, Arbitrary<E2> a2, Arbitrary<E3> a3, Arbitrary<E4> a4, ) combine5<(E1, E2, E3, E4, E5)>( Arbitrary<E1> a1, Arbitrary<E2> a2, Arbitrary<E3> a3, Arbitrary<E4> a4, Arbitrary<E5> a5, ) combine6<(E1, E2, E3, E4, E5, E6)>( Arbitrary<E1> a1, Arbitrary<E2> a2, Arbitrary<E3> a3, Arbitrary<E4> a4, Arbitrary<E5> a5, Arbitrary<E6> a6, ) combine7<(E1, E2, E3, E4, E5, E6, E7)>( Arbitrary<E1> a1, Arbitrary<E2> a2, Arbitrary<E3> a3, Arbitrary<E4> a4, Arbitrary<E5> a5, Arbitrary<E6> a6, Arbitrary<E7> a7, ) combine8<(E1, E2, E3, E4, E5, E6, E7, E8)>( Arbitrary<E1> a1, Arbitrary<E2> a2, Arbitrary<E3> a3, Arbitrary<E4> a4, Arbitrary<E5> a5, Arbitrary<E6> a6, Arbitrary<E7> a7, Arbitrary<E8> a8, )

Various combine functions generate values as record simultaneously from multiple arbitraries. You can combine 2 to 8 arbitraries.

If you want to process the generated data before passing it to the test block, you should use map.

Example: Generating a Point from two integers

property('generate point from integers', () { forAll( combine2(integer(), integer()).map((x, y) => Point(x, y)), (point) { expect(point, isA<Point>()); }, ); });

Shrinking is done sequentially for each arbitrary.

oneOf

Arbitrary<T> oneOf<T>(List<Arbitrary<T>> arbitraries)

Randomly selects and generates a value from arbitraries in each iteration.

Shrinking is performed on the value that caused an error. No other arbitraries that did not generate this value are used for shrinking.

frequency

Arbitrary<dynamic> frequency(List<(int, Arbitrary<dynamic>)> arbitraries)

Randomly selects and generates a value from arbitraries in each iteration, where the first element of each record in arbitraries is the weight. The probability of selecting each arbitrary is proportional to its weight relative to the total weight.

Example:

property('integer or float', () { forAll( frequency([ (1, integer()), (2, float()), ]), (value) { expect(value, anyOf(isA<int>(), isA<double>())); }, ); });

Shrinking is performed on the value that caused an error. No other arbitraries that did not generate this value are used for shrinking.

recursive

Arbitrary<T> recursive<T>( Arbitrary<T> Function() base, Arbitrary<T> Function(Arbitrary<T>) extend, int? maxDepth, )

Generates values for recursive data structures.

base is a function that returns the base case arbitrary, which is executed first. extend is a function that returns an arbitrary for extending the recursion, taking as an argument the value generated by the base or the previous extend.

maxDepth is the maximum depth of recursion. The default is 5. Increasing the depth of recursion can significantly increase the time required for data generation.

Example: Generating a recursive list of integers

property('dynamic', () { forAll( recursive<dynamic>(() => integer(), (f) => () => list(f())), (value) { expect(value, anyOf(isEmpty, isA<int>(), isA<List<dynamic>>())); }, ); });

Shrinking first occurs within the generated arbitrary. Then, the depth is reduced by one, and the arbitrary is regenerated to use the values it generates. This alternates until the depth reaches zero.

deck

Arbitrary<Deck> deck()

This arbitrary itself does not generate specific values but creates a Deck that can generate values using any arbitrary. In the test block, specifying any arbitrary as an argument to Deck.draw will generate values using that arbitrary. See also Interactive generation.

Shrinking is performed on the value that caused an error. No other arbitraries that did not generate this value are used for shrinking.

build

Arbitrary<T> build<T>(T Function() builder)

Generates a value using the provided builder.

Examples from this arbitrary will never be shrunk.

Example manipulation

Methods to manipulate values generated by arbitraries.

Transform examples

The map method allows transforming values generated by an arbitrary. The definition of map is as follows:

Arbitrary<U> map<U>(U Function(T) f)

The following example generates a queue from the generated list.

property('generate queue from list', () { forAll(list(integer()).map((list) => Queue.of(list)), (queue) { expect(queue, isA<Queue>()); }); });

Even without using map, a queue can be generated within the test block using a list generated by list(integer()). The difference with map is that map creates a new arbitrary, which can be combined with other arbitraries.

Shrinking is performed on the value before transformation.

Create new arbitraries from examples

The flatMap method allows for the creation of new arbitraries using the values generated by another arbitrary. The definition of flatMap is as follows:

Arbitrary<U> flatMap<U>(Arbitrary<U> Function(T) f);

The example below creates an arbitrary that generates lists with a minimum length determined by the generated integer.

property('create list arbitrary with min length from integer', () { forAll( integer().flatMap((value) => list(integer(), minLength: value)), (list) { expect(list, anyOf(isA<List<int>>(), isEmpty)); }, ); });

Shrinking is performed by the generated arbitrary.

Filter examples

The filter method allows for the generation of values that only meet a certain condition. The definition of filter is as follows:

Arbitrary<T> filter(bool Function(T) predicate)

The following example generates only even integers:

property('generation', () { forAll( integer(min: 50, max: 100).filter((e) => e.isEven), (value) { expect(value.isEven, isTrue); }, ); });

Generation continues until the maxExamples is reached. However, if the number of generation attempts reaches maxTries, it results in an error.

Shrinking is also performed only on values that meet the condition.

Interactive generation

Using deck, values can be generated during the execution of the test block using any arbitrary.

Simple example: The following generates two integers during the test and creates a Point. The same process can be written using combine.

property('generate point with deck', () { forAll( deck(), (deck) { final x = deck.draw<int>(integer()); final y = deck.draw<int>(integer()); final point = Point(x, y); }, ); });

Complex example: Generates an integer and changes the next arbitrary to use based on whether the integer is even or odd.

property('generate even integer', () { forAll( deck(), (deck) { final base = deck.draw(integer()); final next = base.isEven ? deck.draw(integer()) : deck.draw(float()); expect(next, anyOf(isA<int>(), isA<double>())); }, ); });

Custom data types

Existing arbitraries can be combined to generate values for data types not directly supported by existing arbitraries.

For generating instances of data types that are derived from a single value or classes that only have one field, it's beneficial to transform the values generated by existing arbitraries using map or flatMap.

If multiple data points are needed, it's effective to use combine to merge multiple arbitraries.

If there is a need to switch between arbitraries based on certain conditions during data generation, using deck is advisable.

Generate values outside of tests

To generate values outside of tests, use the Arbitrary.example method.

T example({RandomState? state, bool edgeCase = false});

The arguments include RandomState and whether to include edge cases. RandomState is an object representing the state of the random number generator (including the seed), and duplicating it allows the same random numbers to be reproduced any number of times.

Examples from this arbitrary will never be shrunk.

Last modified: 13 September 2024