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