My goal is to make my unit tests easy to understand. Currently, they are hard to understand because they have so many nested functions.
I want to use build_runner to generate the code of the unit with all functions unwrapped.
So here is an example of my current test:
test.dart
import 'package:example_usage/src/unwrap.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
class Cat {
String sound() => "Meow";
int walk() => 4;
}
class Dog {
final Cat cat;
Dog(this.cat);
String sayHi() {
return this.cat.sound();
}
int jump() {
return this.cat.walk();
}
}
class MockCat extends Mock implements Cat {}
void main() {
MockCat cat;
Dog dog;
@UnWrap()
void setupCatSoundStub() {
when(cat.sound()).thenReturn("Woof");
}
@UnWrap()
void setupCatWalkstub() {
when(cat.walk()).thenReturn(2);
}
@UnWrap()
void expectCatCalled() {
verify(cat.sound());
}
@UnWrap()
void testDogWoof() {
setupCatSoundStub();
dog = Dog(cat);
final sound = dog.sayHi();
expect(sound, "Woof");
expectCatCalled();
}
void expectCatWalked() {
verify(cat.walk());
}
group('Dog Cat Play', () {
setUp(() {
cat = MockCat();
});
test('Dog woof', () {
testDogWoof();
});
test('Dog woof then jump', () {
testDogWoof();
setupCatWalkstub();
final steps = dog.jump();
expect(steps, 2);
expectCatWalked();
});
});
}
I want to generate a code like this
_$test.dart
void _$main() {
MockCat cat;
Dog dog;
void expectCatWalked() {
verify(cat.walk());
}
group('Dog Cat Play', () {
setUp(() {
cat = MockCat();
});
test('Dog woof', () {
// testDogWoof();
// setupCatSoundStub();
when(cat.sound()).thenReturn("Woof");
dog = Dog(cat);
final sound = dog.sayHi();
expect(sound, "Woof");
// expectCatCalled();
verify(cat.sound());
});
test('Dog woof then jump', () {
// testDogWoof();
// setupCatSoundStub();
when(cat.sound()).thenReturn("Woof");
dog = Dog(cat);
final sound = dog.sayHi();
expect(sound, "Woof");
// expectCatCalled();
verify(cat.sound());
// setupCatWalkstub();
when(cat.walk()).thenReturn(2);
final steps = dog.jump();
expect(steps, 2);
expectCatWalked();
});
});
}
I found some tutorial online but I could find documentations about getting the function body into string ( some like JavaScript's Function.prototype.toString() method ) I am new to code generation so I tried to print all fields but I can't find anything like that.
import 'dart:async';
import 'package:analyzer/dart/element/element.dart';
import 'package:build/build.dart';
import 'package:source_gen/source_gen.dart';
class InfoGenerator extends Generator {
@override
FutureOr<String> generate(LibraryReader library, BuildStep buildStep) {
var buffer = StringBuffer();
// library.allElements.forEach((element) {
// buffer.writeln(
// '// ${element.displayName} - ${element.source.fullName} - ${element.declaration}');
// });
library.allElements.whereType<TopLevelVariableElement>().forEach((element) {
buffer.writeln('/*');
buffer.writeln(element.toString());
buffer.writeln('*/');
buffer.writeln(
'// ${element.name} - ${element.kind.displayName} - ${element.declaration}');
});
return buffer.toString();
}
}
I am also new to annotations so I just made this up
/// What to do here ?
class UnWrap {
const UnWrap();
}
Is what I am trying to do even possible ?
When I first posted this in 2021, I was in the same boat—struggling to extract the function body as a String.
Venturing into the realm of code generators is not for the faint of heart. It’s a challenging endeavor, far more complex than managing state with silver-plated tools like GetX or dealing with the intricacies of Flutter's navigation. It makes taming the MediaQuery or battling with Flexible and Expanded widgets look like a walk in the park.
So here I share in single working project, worth hours of effort and countless struggles, to help anyone.
https://github.com/tolotrasamuel/func_unwrapper
What you are looking for is https://github.com/tolotrasamuel/func_unwrapper/blob/e94acf925741d57bfcf49bc4ebe933be1e1795b2/my_generators/lib/src/function_unwrap.dart#L262
Block? getFuncBodyFromFuncDeclaration(FunctionDeclaration astNode) {
final funcExpression =
astNode.childEntities.whereType<FunctionExpression>().firstOrNull;
final blockFuncBody = funcExpression?.childEntities
.whereType<BlockFunctionBody>()
.firstOrNull;
final block = blockFuncBody?.childEntities.whereType<Block>().firstOrNull;
// final blockExpression = block.childEntities.whereType<ExpressionStatement>().first;
return block;
}
You can directly use it, the Block class has a function or a getter to get the function body directly as String. But alternatively, it also contains the offset of the first and last character of the function from the original source file.
static Selector getBlockSelector(Block withElementWithOriginalOffset) {
final statements = withElementWithOriginalOffset.childEntities
.where((e) => (e is AstNode));
if (statements.isEmpty) return Selector(0, 0);
return Selector(statements.first.offset, statements.last.end);
}
Check out this line to know how to read the original source file as String.
https://github.com/tolotrasamuel/func_unwrapper/blob/e94acf925741d57bfcf49bc4ebe933be1e1795b2/my_generators/lib/src/function_unwrap.dart#L74C1-L84C1
I preferred using the second option because it was easier for my purpose.
Future<String?> readAsString(AssetId inputId) async {
try {
return await buildStep.readAsString(inputId);
} catch (e, trace) {
print('Possible BuildStepCompletedException $e');
print(trace);
return null;
}
}
Good luck!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With