source_gen_test
source_gen_test copied to clipboard
[FeatureRequest] More fully functional BuildStep for tests
Hi there 👋 First off, thanks for the great package!
Note: The below context is maybe helpful but not necessary for the overall question. The TLDR question is at the very bottom of this issue.
I have a generator whose purpose is to act on annotated export and function Element
s and create fakes of the Type
passed into the annotation. It's not too dissimilar to the mockito
package.
The usage of the annotation looks like:
@GenerateGraphqlFakes([GHomeData])
export 'my_feature.fakes.dart';
where GHomeData
is a type that needs its fake generated. For most of that work, I use a *.data.gql.dart
file (generated by ferry
), but in some cases there are "special" types whose factories can not be derived simply using the above file. For those, the consumer of my Builder
provides a file name (e.g special_type.dart
) that is looked up using findAssets
on BuildStep
. This works at runtime but tests use MockBuildStep
which breaks when anything is called on it. To work around this, I've implemented a wrapper generator that injects a FakeBuildStep
that implements select functions (specific to my needs). That looks like this:
// in a test
class GeneratorWithFakeBuildStep extends GeneratorForAnnotation<GenerateGraphqlFakes> {
final FactoryGeneratorConfig config;
final String customTypesDirectory;
GeneratorWithFakeBuildStep({
required this.config,
required this.customTypesDirectory,
});
@override
Future<String> generateForAnnotatedElement(
Element element,
ConstantReader annotation,
BuildStep buildStep,
) {
final generator = FactoryGenerator(config);
return generator.generateForAnnotatedElement(
element,
annotation,
FakeBuildStep(customTypesDirectory),
);
}
}
FakeBuildStep
itself does something similar to what is done when initializing a test LibraryReader
:
// fake_build_step.dart
// ignore: subtype_of_sealed_class
class FakeBuildStep extends BuildStep {
final String testDirectory;
late final Map<String, String> assets;
FakeBuildStep(this.testDirectory) {
final map = Map.fromEntries(
Directory(testDirectory)
.listSync(recursive: true)
.whereType<File>()
.map((f) => MapEntry(f.path, f.readAsStringSync())),
);
String assetIdForFile(String fileName) => '$testPackageName|lib/$fileName';
assets = map.map((file, content) => MapEntry(assetIdForFile(file), content));
}
...
}
My implementation of findAssets
is as follows:
@override
Stream<AssetId> findAssets(Glob glob) {
final keys = assets.keys;
final matches = keys.expand((assetId) {
return glob.allMatches(assetId);
});
return Stream.fromIterable(
matches.map(
(match) => AssetId.parse(match.input),
),
);
}
So, my primary question is: Is there a reason (other than simplicity) that MockBuildStep
was used as the stand-in for the runtime BuildStep
? If not, is there any interest in building out a more robust fake that would allow consumers to not need to use a workaround like the above? Or better yet, am I just doing something wrong?