kevmoo / source_gen_test

Test APIs to make it easier to create robust test for package:source_gen Generators
https://pub.dev/packages/source_gen_test
MIT License
18 stars 10 forks source link

[FeatureRequest] More fully functional BuildStep for tests #45

Open btrautmann opened 1 year ago

btrautmann commented 1 year ago

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 Elements 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?

Jure-BB commented 1 year ago

In my case I need buildStep.resolver.astNodeFor().