testBuilder function Null safety

Future testBuilder(
  1. Builder builder,
  2. Map<String, Object> sourceAssets,
  3. {Set<String>? generateFor,
  4. bool isInput(
    1. String assetId
  5. String? rootPackage,
  6. MultiPackageAssetReader? reader,
  7. RecordingAssetWriter? writer,
  8. Map<String, Object>? outputs,
  9. void onLog(
    1. LogRecord log
  10. void reportUnusedAssetsForInput(
    1. AssetId,
    2. Iterable<AssetId>
  11. PackageConfig? packageConfig}

Runs builder in a test environment.

The test environment supplies in-memory build sourceAssets to the builders under test.

outputs may be optionally provided to verify that the builders produce the expected output, see checkOutputs for a full description of the outputs map and how to use it. If outputs is omitted the only validation this method provides is that the build did not throw.

Either generateFor or the isInput callback can specify which assets should be given as inputs to the builder. These can be omitted if every asset in sourceAssets should be considered an input. generateFor is ignored if both isInput and generateFor are provided.

The keys in sourceAssets and outputs are paths to file assets and the values are file contents. The paths must use the following format:


Where PACKAGE_NAME is the name of the package, and PATH_WITHIN_PACKAGE is the path to a file relative to the package. PATH_WITHIN_PACKAGE must include lib, web, bin or test. Example: "myapp|lib/utils.dart".

If a reader is provided, then any asset not in sourceAssets will be read from the provided reader. This allows you to more easily provide sources of entire packages to the test, instead of mocking them out, for example, this exposes all assets available to the test itself:

testBuilder(yourBuilder, {}/* test assets here */,
    reader: await PackageAssetReader.currentIsolate());

Callers may optionally provide a writer to stub different behavior or do more complex validation than what is possible with outputs.

Callers may optionally provide an onLog callback to do validaiton on the logging output of the builder.

An optional packageConfig may be supplied to set the language versions of certain packages. It will only be used for this purpose and not for reading of files or converting uris.

Enabling of language experiments is supported through the withEnabledExperiments method from package:build.


Future testBuilder(
    Builder builder, Map<String, /*String|List<int>*/ Object> sourceAssets,
    {Set<String>? generateFor,
    bool Function(String assetId)? isInput,
    String? rootPackage,
    MultiPackageAssetReader? reader,
    RecordingAssetWriter? writer,
    Map<String, /*String|List<int>|Matcher<List<int>>*/ Object>? outputs,
    void Function(LogRecord log)? onLog,
    void Function(AssetId, Iterable<AssetId>)? reportUnusedAssetsForInput,
    PackageConfig? packageConfig}) async {
  writer ??= InMemoryAssetWriter();

  var inputIds = {
    for (var descriptor in sourceAssets.keys) makeAssetId(descriptor)
  var allPackages = {for (var id in inputIds) id.package};
  if (allPackages.length == 1) rootPackage ??= allPackages.first;

    for (var package in allPackages) AssetId(package, r'lib/$lib$'),
    if (rootPackage != null) ...[
      AssetId(rootPackage, r'$package$'),
      AssetId(rootPackage, r'test/$test$'),
      AssetId(rootPackage, r'web/$web$'),

  final inMemoryReader = InMemoryAssetReader(rootPackage: rootPackage);

  sourceAssets.forEach((serializedId, contents) {
    var id = makeAssetId(serializedId);
    if (contents is String) {
      inMemoryReader.cacheStringAsset(id, contents);
    } else if (contents is List<int>) {
      inMemoryReader.cacheBytesAsset(id, contents);

  final inputFilter = isInput ?? generateFor?.contains ?? (_) => true;
  inputIds.retainWhere((id) => inputFilter('$id'));

  var writerSpy = AssetWriterSpy(writer);
  var logger = Logger('testBuilder');
  var logSubscription = logger.onRecord.listen(onLog);
  var resolvers = packageConfig == null && enabledExperiments.isEmpty
      ? defaultResolvers
      : AnalyzerResolvers(null, null, packageConfig);

  for (var input in inputIds) {
    // create another writer spy and reader for each input. This prevents writes
    // from a previous input being readable when processing the current input.
    final spyForStep = AssetWriterSpy(writerSpy);
    final readerForStep = MultiAssetReader([
      if (reader != null) reader,
      WrittenAssetReader(writer, spyForStep),

    await runBuilder(builder, {input}, readerForStep, spyForStep, resolvers,
        logger: logger, reportUnusedAssetsForInput: reportUnusedAssetsForInput);

  await logSubscription.cancel();
  var actualOutputs = writerSpy.assetsWritten;
  checkOutputs(outputs, actualOutputs, writer);