Skip to content

Commit

Permalink
Support mocking methods with return types declared in private SDK lib…
Browse files Browse the repository at this point in the history
…raries.

Examples include HttpClient and WebSocket.

Fixes #405

PiperOrigin-RevId: 374237555
  • Loading branch information
srawlins committed May 19, 2021
1 parent fbc6b84 commit 6de2746
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 5 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

* Migrate Mockito codegen to null safety.
* Support mocking methods with typed_data List return types.
* Support mocking methods with return types declared in private SDK libraries
(such as HttpClient and WebSocket, declared in `dart:_http`).

## 5.0.7

Expand Down
42 changes: 37 additions & 5 deletions lib/src/builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import 'dart:collection';

import 'package:analyzer/dart/constant/value.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart' as analyzer;
Expand Down Expand Up @@ -57,8 +59,8 @@ class MockBuilder implements Builder {
final mockTargetGatherer = _MockTargetGatherer(entryLib);

var entryAssetId = await buildStep.resolver.assetIdForElement(entryLib);
final assetUris = await _resolveAssetUris(
buildStep.resolver, mockTargetGatherer._mockTargets, entryAssetId.path);
final assetUris = await _resolveAssetUris(buildStep.resolver,
mockTargetGatherer._mockTargets, entryAssetId.path, entryLib);

final mockLibraryInfo = _MockLibraryInfo(mockTargetGatherer._mockTargets,
assetUris: assetUris, entryLib: entryLib);
Expand Down Expand Up @@ -102,17 +104,22 @@ $rawOutput
await buildStep.writeAsString(mockLibraryAsset, mockLibraryContent);
}

Future<Map<Element, String>> _resolveAssetUris(Resolver resolver,
List<_MockTarget> mockTargets, String entryAssetPath) async {
Future<Map<Element, String>> _resolveAssetUris(
Resolver resolver,
List<_MockTarget> mockTargets,
String entryAssetPath,
LibraryElement entryLib) async {
final typeVisitor = _TypeVisitor();
final seenTypes = <analyzer.InterfaceType>{};
final librariesWithTypes = <LibraryElement>{};

void addTypesFrom(analyzer.InterfaceType type) {
// Prevent infinite recursion.
if (seenTypes.contains(type)) {
return;
}
seenTypes.add(type);
librariesWithTypes.add(type.element.library);
type.element.accept(typeVisitor);
// For a type like `Foo<Bar>`, add the `Bar`.
type.typeArguments
Expand All @@ -133,7 +140,12 @@ $rawOutput
for (var element in typeVisitor._elements) {
final elementLibrary = element.library!;
if (elementLibrary.isInSdk) {
typeUris[element] = elementLibrary.source.uri.toString();
if (elementLibrary.name!.startsWith('dart._')) {
typeUris[element] = _findPublicExportOf(
Queue.of(librariesWithTypes), elementLibrary)!;
} else {
typeUris[element] = elementLibrary.source.uri.toString();
}
continue;
}

Expand All @@ -156,6 +168,26 @@ $rawOutput
return typeUris;
}

/// Returns the String import path of the correct public library which
/// exports [privateLibrary], selecting from the imports of [inputLibraries].
static String? _findPublicExportOf(
Queue<LibraryElement> inputLibraries, LibraryElement privateLibrary) {
final libraries = Queue.of([
for (final library in inputLibraries) ...library.importedLibraries,
]);

while (libraries.isNotEmpty) {
final library = libraries.removeFirst();
if (library.exportedLibraries.contains(privateLibrary)) {
return library.source.uri.toString();
}
// A library may provide [privateLibrary] by exporting a library which
// provides it (directly or via further exporting).
libraries.addAll(library.exportedLibraries);
}
return null;
}

@override
final buildExtensions = const {
'.dart': ['.mocks.dart']
Expand Down
38 changes: 38 additions & 0 deletions test/builder/auto_mocks_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1039,6 +1039,44 @@ void main() {
expect(mocksContent, contains('_i2.Callback3<_i2.Foo>? c'));
});

test('imports libraries for types declared in private SDK libraries',
() async {
var mocksContent = await buildWithSingleNonNullableSource(dedent('''
import 'dart:io';
abstract class Foo {
HttpClient f() {}
}
'''));
expect(mocksContent, contains("import 'dart:io' as _i2;"));
expect(mocksContent, contains('_i2.HttpClient f() =>'));
});

test(
'imports libraries for types declared in private SDK libraries exported '
'in dart:io', () async {
var mocksContent = await buildWithSingleNonNullableSource(dedent('''
import 'dart:io';
abstract class Foo {
HttpStatus f() {}
}
'''));
expect(mocksContent, contains("import 'dart:io' as _i2;"));
expect(mocksContent, contains('_i2.HttpStatus f() =>'));
});

test(
'imports libraries for types declared in private SDK libraries exported '
'in dart:html', () async {
var mocksContent = await buildWithSingleNonNullableSource(dedent('''
import 'dart:html';
abstract class Foo {
HttpStatus f() {}
}
'''));
expect(mocksContent, contains("import 'dart:html' as _i2;"));
expect(mocksContent, contains('_i2.HttpStatus f() =>'));
});

test('prefixes parameter type on generic function-typed parameter', () async {
await expectSingleNonNullableOutput(
dedent(r'''
Expand Down

0 comments on commit 6de2746

Please sign in to comment.