blob: 5eb446a407c91215f6482056a957f3e64d4544ad [file] [log] [blame]
// Copyright 2019 Dart Mockito authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import 'package:build/build.dart';
import 'package:build_test/build_test.dart';
import 'package:meta/meta.dart';
import 'package:mockito/src/builder.dart';
import 'package:test/test.dart';
Builder buildMocks(BuilderOptions options) => MockBuilder();
const annotationsAsset = {
'mockito|lib/annotations.dart': '''
class GenerateMocks {
final List<Type> classes;
const GenerateMocks(this.classes);
}
'''
};
const simpleTestAsset = {
'foo|test/foo_test.dart': '''
import 'package:foo/foo.dart';
import 'package:mockito/annotations.dart';
@GenerateMocks([Foo])
void main() {}
'''
};
void main() {
test(
'generates mock for an imported class but does not override private '
'or static methods or methods w/ zero parameters', () async {
await testBuilder(
buildMocks(BuilderOptions({})),
{
...annotationsAsset,
...simpleTestAsset,
'foo|lib/foo.dart': dedent(r'''
class Foo {
dynamic a() => 7;
int _b(int x) => 8;
static int c(int y) => 9;
}
'''),
},
outputs: {
'foo|test/foo_test.mocks.dart': dedent(r'''
import 'package:mockito/mockito.dart' as _i1;
import 'package:foo/foo.dart' as _i2;
/// A class which mocks [Foo].
///
/// See the documentation for Mockito's code generation for more information.
class MockFoo extends _i1.Mock implements _i2.Foo {}
'''),
},
);
});
test(
'generates mock for an imported class but does not override private '
'or static fields', () async {
await testBuilder(
buildMocks(BuilderOptions({})),
{
...annotationsAsset,
...simpleTestAsset,
'foo|lib/foo.dart': dedent(r'''
class Foo {
int _a;
static int b;
}
'''),
},
outputs: {
'foo|test/foo_test.mocks.dart': dedent(r'''
import 'package:mockito/mockito.dart' as _i1;
import 'package:foo/foo.dart' as _i2;
/// A class which mocks [Foo].
///
/// See the documentation for Mockito's code generation for more information.
class MockFoo extends _i1.Mock implements _i2.Foo {}
'''),
},
);
});
test(
'generates mock for an imported class but does not override any '
'extension methods', () async {
await testBuilder(
buildMocks(BuilderOptions({})),
{
...annotationsAsset,
...simpleTestAsset,
'foo|lib/foo.dart': dedent(r'''
extension X on Foo {
dynamic x(int m, String n) => n + 1;
}
class Foo {
dynamic a(int m, String n) => n + 1;
}
'''),
},
outputs: {
'foo|test/foo_test.mocks.dart': dedent(r'''
import 'package:mockito/mockito.dart' as _i1;
import 'package:foo/foo.dart' as _i2;
/// A class which mocks [Foo].
///
/// See the documentation for Mockito's code generation for more information.
class MockFoo extends _i1.Mock implements _i2.Foo {
dynamic a(int m, String n) =>
super.noSuchMethod(Invocation.method(#a, [m, n]));
}
'''),
},
);
});
test('generates a mock class and overrides methods parameters', () async {
await testBuilder(
buildMocks(BuilderOptions({})),
{
...annotationsAsset,
...simpleTestAsset,
'foo|lib/foo.dart': dedent(r'''
class Foo {
dynamic a(int m, String n) => n + 1;
dynamic b(List<int> list) => list.length;
void c(String one, [String two, String three = ""]) => print('$one$two$three');
void d(String one, {String two, String three = ""}) => print('$one$two$three');
Future<void> e(String s) async => print(s);
// TODO(srawlins): Figure out async*; doesn't work yet. `isGenerator`
// does not appear to be working.
// Stream<void> f(String s) async* => print(s);
// Iterable<void> g(String s) sync* => print(s);
}
'''),
},
outputs: {
'foo|test/foo_test.mocks.dart': dedent(r'''
import 'package:mockito/mockito.dart' as _i1;
import 'package:foo/foo.dart' as _i2;
import 'dart:async' as _i3;
/// A class which mocks [Foo].
///
/// See the documentation for Mockito's code generation for more information.
class MockFoo extends _i1.Mock implements _i2.Foo {
dynamic a(int m, String n) =>
super.noSuchMethod(Invocation.method(#a, [m, n]));
dynamic b(List<int> list) =>
super.noSuchMethod(Invocation.method(#b, [list]));
void c(String one, [String two, String three = ""]) =>
super.noSuchMethod(Invocation.method(#c, [one, two, three]));
void d(String one, {String two, String three = ""}) => super
.noSuchMethod(Invocation.method(#d, [one], {#two: two, #three: three}));
_i3.Future<void> e(String s) async =>
super.noSuchMethod(Invocation.method(#e, [s]));
}
'''),
},
);
});
test('generates multiple mock classes', () async {
await testBuilder(
buildMocks(BuilderOptions({})),
{
...annotationsAsset,
'foo|lib/foo.dart': dedent(r'''
class Foo {
dynamic a(int m, String n) => n + 1;
}
class Bar {
dynamic b(List<int> list) => list.length;
}
'''),
'foo|test/foo_test.dart': '''
import 'package:foo/foo.dart';
import 'package:mockito/annotations.dart';
@GenerateMocks([Foo, Bar])
void main() {}
'''
},
outputs: {
'foo|test/foo_test.mocks.dart': dedent(r'''
import 'package:mockito/mockito.dart' as _i1;
import 'package:foo/foo.dart' as _i2;
/// A class which mocks [Foo].
///
/// See the documentation for Mockito's code generation for more information.
class MockFoo extends _i1.Mock implements _i2.Foo {
dynamic a(int m, String n) =>
super.noSuchMethod(Invocation.method(#a, [m, n]));
}
/// A class which mocks [Bar].
///
/// See the documentation for Mockito's code generation for more information.
class MockBar extends _i1.Mock implements _i2.Bar {
dynamic b(List<int> list) =>
super.noSuchMethod(Invocation.method(#b, [list]));
}
'''),
},
);
});
test('generates generic mock classes', () async {
await testBuilder(
buildMocks(BuilderOptions({})),
{
...annotationsAsset,
'foo|lib/foo.dart': dedent(r'''
class Foo<T, U> {
dynamic a(int m) => m + 1;
}
'''),
'foo|test/foo_test.dart': '''
import 'package:foo/foo.dart';
import 'package:mockito/annotations.dart';
@GenerateMocks([Foo])
void main() {}
'''
},
outputs: {
'foo|test/foo_test.mocks.dart': dedent(r'''
import 'package:mockito/mockito.dart' as _i1;
import 'package:foo/foo.dart' as _i2;
/// A class which mocks [Foo].
///
/// See the documentation for Mockito's code generation for more information.
class MockFoo<T, U> extends _i1.Mock implements _i2.Foo<T, U> {
dynamic a(int m) => super.noSuchMethod(Invocation.method(#a, [m]));
}
'''),
},
);
});
test('generates generic mock classes with type bounds', () async {
await testBuilder(
buildMocks(BuilderOptions({})),
{
...annotationsAsset,
'foo|lib/foo.dart': dedent(r'''
class Foo {
dynamic a(int m) => m + 1;
}
class Bar<T extends Foo> {
dynamic b(int m) => m + 1;
}
'''),
'foo|test/foo_test.dart': '''
import 'package:foo/foo.dart';
import 'package:mockito/annotations.dart';
@GenerateMocks([Foo, Bar])
void main() {}
'''
},
outputs: {
'foo|test/foo_test.mocks.dart': dedent(r'''
import 'package:mockito/mockito.dart' as _i1;
import 'package:foo/foo.dart' as _i2;
/// A class which mocks [Foo].
///
/// See the documentation for Mockito's code generation for more information.
class MockFoo extends _i1.Mock implements _i2.Foo {
dynamic a(int m) => super.noSuchMethod(Invocation.method(#a, [m]));
}
/// A class which mocks [Bar].
///
/// See the documentation for Mockito's code generation for more information.
class MockBar<T extends _i2.Foo> extends _i1.Mock implements _i2.Bar<T> {
dynamic b(int m) => super.noSuchMethod(Invocation.method(#b, [m]));
}
'''),
},
);
});
test('imports libraries for external class types', () async {
await testBuilder(
buildMocks(BuilderOptions({})),
{
...annotationsAsset,
...simpleTestAsset,
'foo|lib/foo.dart': dedent(r'''
import 'dart:async';
class Foo {
dynamic f(List<Foo> list) {}
}
'''),
},
outputs: {
'foo|test/foo_test.mocks.dart': dedent(r'''
import 'package:mockito/mockito.dart' as _i1;
import 'package:foo/foo.dart' as _i2;
/// A class which mocks [Foo].
///
/// See the documentation for Mockito's code generation for more information.
class MockFoo extends _i1.Mock implements _i2.Foo {
dynamic f(List<_i2.Foo> list) =>
super.noSuchMethod(Invocation.method(#f, [list]));
}
'''),
},
);
});
test('imports libraries for type aliases with external types', () async {
await testBuilder(
buildMocks(BuilderOptions({})),
{
...annotationsAsset,
...simpleTestAsset,
'foo|lib/foo.dart': dedent(r'''
import 'dart:async';
typedef Callback = void Function();
typedef void Callback2();
typedef Future<T> Callback3<T>();
class Foo {
dynamic f(Callback c) {}
dynamic g(Callback2 c) {}
dynamic h(Callback3<Foo> c) {}
}
'''),
},
outputs: {
'foo|test/foo_test.mocks.dart': dedent(r'''
import 'package:mockito/mockito.dart' as _i1;
import 'package:foo/foo.dart' as _i2;
/// A class which mocks [Foo].
///
/// See the documentation for Mockito's code generation for more information.
class MockFoo extends _i1.Mock implements _i2.Foo {
dynamic f(_i2.Callback c) => super.noSuchMethod(Invocation.method(#f, [c]));
dynamic g(_i2.Callback2 c) => super.noSuchMethod(Invocation.method(#g, [c]));
dynamic h(_i2.Callback3<_i2.Foo> c) =>
super.noSuchMethod(Invocation.method(#h, [c]));
}
'''),
},
);
});
test('imports libraries for function types with external types', () async {
await testBuilder(
buildMocks(BuilderOptions({})),
{
...annotationsAsset,
...simpleTestAsset,
'foo|lib/foo.dart': dedent(r'''
import 'dart:async';
class Foo {
dynamic f(Foo c()) {}
dynamic g(void c(Foo f)) {}
dynamic h(void c(Foo f, [Foo g])) {}
dynamic i(void c(Foo f, {Foo g})) {}
dynamic j(Foo Function() c) {}
dynamic k(void Function(Foo f) c) {}
}
'''),
},
outputs: {
'foo|test/foo_test.mocks.dart': dedent(r'''
import 'package:mockito/mockito.dart' as _i1;
import 'package:foo/foo.dart' as _i2;
/// A class which mocks [Foo].
///
/// See the documentation for Mockito's code generation for more information.
class MockFoo extends _i1.Mock implements _i2.Foo {
dynamic f(_i2.Foo Function() c) =>
super.noSuchMethod(Invocation.method(#f, [c]));
dynamic g(void Function(_i2.Foo) c) =>
super.noSuchMethod(Invocation.method(#g, [c]));
dynamic h(void Function(_i2.Foo, [_i2.Foo]) c) =>
super.noSuchMethod(Invocation.method(#h, [c]));
dynamic i(void Function(_i2.Foo, {_i2.Foo g}) c) =>
super.noSuchMethod(Invocation.method(#i, [c]));
dynamic j(_i2.Foo Function() c) =>
super.noSuchMethod(Invocation.method(#j, [c]));
dynamic k(void Function(_i2.Foo) c) =>
super.noSuchMethod(Invocation.method(#k, [c]));
}
'''),
},
);
});
test('overrides absrtact methods', () async {
await testBuilder(
buildMocks(BuilderOptions({})),
{
...annotationsAsset,
...simpleTestAsset,
'foo|lib/foo.dart': dedent(r'''
abstract class Foo {
dynamic f(int a);
dynamic _g(int a);
}
'''),
},
outputs: {
'foo|test/foo_test.mocks.dart': dedent(r'''
import 'package:mockito/mockito.dart' as _i1;
import 'package:foo/foo.dart' as _i2;
/// A class which mocks [Foo].
///
/// See the documentation for Mockito's code generation for more information.
class MockFoo extends _i1.Mock implements _i2.Foo {
dynamic f(int a) => super.noSuchMethod(Invocation.method(#f, [a]));
}
'''),
},
);
});
test('overrides generic methods', () async {
await testBuilder(
buildMocks(BuilderOptions({})),
{
...annotationsAsset,
...simpleTestAsset,
'foo|lib/foo.dart': dedent(r'''
class Foo {
dynamic f<T>(int a) {}
dynamic g<T extends Foo>(int a) {}
}
'''),
},
outputs: {
// TODO(srawlins): The getter will appear when it has a non-nullable
// return type.
'foo|test/foo_test.mocks.dart': dedent(r'''
import 'package:mockito/mockito.dart' as _i1;
import 'package:foo/foo.dart' as _i2;
/// A class which mocks [Foo].
///
/// See the documentation for Mockito's code generation for more information.
class MockFoo extends _i1.Mock implements _i2.Foo {
dynamic f<T>(int a) => super.noSuchMethod(Invocation.method(#f, [a]));
dynamic g<T extends _i2.Foo>(int a) =>
super.noSuchMethod(Invocation.method(#g, [a]));
}
'''),
},
);
});
test('overrides getters and setters', () async {
await testBuilder(
buildMocks(BuilderOptions({})),
{
...annotationsAsset,
...simpleTestAsset,
'foo|lib/foo.dart': dedent(r'''
class Foo {
int get a => n + 1;
int _b;
set b(int value) => _b = value;
}
'''),
},
outputs: {
// TODO(srawlins): The getter will appear when it has a non-nullable
// return type.
'foo|test/foo_test.mocks.dart': dedent(r'''
import 'package:mockito/mockito.dart' as _i1;
import 'package:foo/foo.dart' as _i2;
/// A class which mocks [Foo].
///
/// See the documentation for Mockito's code generation for more information.
class MockFoo extends _i1.Mock implements _i2.Foo {
set b(int value) => super.noSuchMethod(Invocation.setter(#b, [value]));
}
'''),
},
);
});
test('overrides operators', () async {
await testBuilder(
buildMocks(BuilderOptions({})),
{
...annotationsAsset,
...simpleTestAsset,
'foo|lib/foo.dart': dedent(r'''
class Foo {
int _b;
int operator +(Foo other) => _b + other._b;
bool operator ==(Object other) => other is Foo && _b == other._b;
}
'''),
},
outputs: {
'foo|test/foo_test.mocks.dart': dedent(r'''
import 'package:mockito/mockito.dart' as _i1;
import 'package:foo/foo.dart' as _i2;
/// A class which mocks [Foo].
///
/// See the documentation for Mockito's code generation for more information.
class MockFoo extends _i1.Mock implements _i2.Foo {
int operator +(_i2.Foo other) =>
super.noSuchMethod(Invocation.method(#+, [other]), 0);
bool operator ==(Object other) =>
super.noSuchMethod(Invocation.method(#==, [other]), false);
}
'''),
},
);
});
test('creates dummy non-null return values for known core classes', () async {
await testBuilder(
buildMocks(BuilderOptions({})),
{
...annotationsAsset,
...simpleTestAsset,
'foo|lib/foo.dart': dedent(r'''
class Foo {
bool m1() => false;
double m2() => 3.14;
int m3() => 7;
String m4() => "Hello";
List<Foo> m5() => [Foo()];
Set<Foo> m6() => {Foo()};
Map<int, Foo> m7() => {7: Foo()};
}
'''),
},
outputs: {
'foo|test/foo_test.mocks.dart': dedent(r'''
import 'package:mockito/mockito.dart' as _i1;
import 'package:foo/foo.dart' as _i2;
/// A class which mocks [Foo].
///
/// See the documentation for Mockito's code generation for more information.
class MockFoo extends _i1.Mock implements _i2.Foo {
bool m1() => super.noSuchMethod(Invocation.method(#m1, []), false);
double m2() => super.noSuchMethod(Invocation.method(#m2, []), 0.0);
int m3() => super.noSuchMethod(Invocation.method(#m3, []), 0);
String m4() => super.noSuchMethod(Invocation.method(#m4, []), '');
List<_i2.Foo> m5() => super.noSuchMethod(Invocation.method(#m5, []), []);
Set<_i2.Foo> m6() => super.noSuchMethod(Invocation.method(#m6, []), {});
Map<int, _i2.Foo> m7() => super.noSuchMethod(Invocation.method(#m7, []), {});
}
'''),
},
);
});
test('creates dummy non-null return values for Futures of known core classes',
() async {
await testBuilder(
buildMocks(BuilderOptions({})),
{
...annotationsAsset,
...simpleTestAsset,
'foo|lib/foo.dart': dedent(r'''
class Foo {
Future<bool> m1() async => false;
}
'''),
},
outputs: {
'foo|test/foo_test.mocks.dart': dedent(r'''
import 'package:mockito/mockito.dart' as _i1;
import 'package:foo/foo.dart' as _i2;
import 'dart:async' as _i3;
/// A class which mocks [Foo].
///
/// See the documentation for Mockito's code generation for more information.
class MockFoo extends _i1.Mock implements _i2.Foo {
_i3.Future<bool> m1() async =>
super.noSuchMethod(Invocation.method(#m1, []), Future.value(false));
}
'''),
},
);
});
test('creates dummy non-null return values for unknown classes', () async {
await testBuilder(
buildMocks(BuilderOptions({})),
{
...annotationsAsset,
...simpleTestAsset,
'foo|lib/foo.dart': dedent(r'''
class Foo {
Bar m1() => Bar('name');
}
class Bar {
final String name;
Bar(this.name);
}
'''),
},
outputs: {
'foo|test/foo_test.mocks.dart': dedent(r'''
import 'package:mockito/mockito.dart' as _i1;
import 'package:foo/foo.dart' as _i2;
class _FakeBar extends _i1.Fake implements _i2.Bar {}
/// A class which mocks [Foo].
///
/// See the documentation for Mockito's code generation for more information.
class MockFoo extends _i1.Mock implements _i2.Foo {
_i2.Bar m1() => super.noSuchMethod(Invocation.method(#m1, []), _FakeBar());
}
'''),
},
);
});
test('deduplicates fake classes', () async {
await testBuilder(
buildMocks(BuilderOptions({})),
{
...annotationsAsset,
...simpleTestAsset,
'foo|lib/foo.dart': dedent(r'''
class Foo {
Bar m1() => Bar('name1');
Bar m2() => Bar('name2');
}
class Bar {
final String name;
Bar(this.name);
}
'''),
},
outputs: {
'foo|test/foo_test.mocks.dart': dedent(r'''
import 'package:mockito/mockito.dart' as _i1;
import 'package:foo/foo.dart' as _i2;
class _FakeBar extends _i1.Fake implements _i2.Bar {}
/// A class which mocks [Foo].
///
/// See the documentation for Mockito's code generation for more information.
class MockFoo extends _i1.Mock implements _i2.Foo {
_i2.Bar m1() => super.noSuchMethod(Invocation.method(#m1, []), _FakeBar());
_i2.Bar m2() => super.noSuchMethod(Invocation.method(#m2, []), _FakeBar());
}
'''),
},
);
});
test('creates dummy non-null return values for enums', () async {
await testBuilder(
buildMocks(BuilderOptions({})),
{
...annotationsAsset,
...simpleTestAsset,
'foo|lib/foo.dart': dedent(r'''
class Foo {
Bar m1() => Bar('name');
}
enum Bar {
one,
two,
}
'''),
},
outputs: {
'foo|test/foo_test.mocks.dart': dedent(r'''
import 'package:mockito/mockito.dart' as _i1;
import 'package:foo/foo.dart' as _i2;
/// A class which mocks [Foo].
///
/// See the documentation for Mockito's code generation for more information.
class MockFoo extends _i1.Mock implements _i2.Foo {
_i2.Bar m1() => super.noSuchMethod(Invocation.method(#m1, []), _i2.Bar.one);
}
'''),
},
);
});
test('creates dummy non-null return values for functions', () async {
await testBuilder(
buildMocks(BuilderOptions({})),
{
...annotationsAsset,
...simpleTestAsset,
'foo|lib/foo.dart': dedent(r'''
class Foo {
void Function(int, [String]) m1() => (int i, [String s]) {};
void Function(Foo, {bool b}) m2() => (Foo f, {bool b}) {};
Foo Function() m3() => () => Foo();
}
'''),
},
outputs: {
'foo|test/foo_test.mocks.dart': dedent(r'''
import 'package:mockito/mockito.dart' as _i1;
import 'package:foo/foo.dart' as _i2;
class _FakeFoo extends _i1.Fake implements _i2.Foo {}
/// A class which mocks [Foo].
///
/// See the documentation for Mockito's code generation for more information.
class MockFoo extends _i1.Mock implements _i2.Foo {
void Function(int, [String]) m1() => super
.noSuchMethod(Invocation.method(#m1, []), (int __p0, [String __p1]) {});
void Function(_i2.Foo, {bool b}) m2() => super
.noSuchMethod(Invocation.method(#m2, []), (_i2.Foo __p0, {bool b}) {});
_i2.Foo Function() m3() =>
super.noSuchMethod(Invocation.method(#m3, []), () => _FakeFoo());
}
'''),
},
);
});
test('throws when GenerateMocks references an unresolved type', () async {
expectBuilderThrows(
assets: {
...annotationsAsset,
'foo|lib/foo.dart': dedent(r'''
class Foo {}
'''),
'foo|test/foo_test.dart': dedent('''
// missing foo.dart import.
import 'package:mockito/annotations.dart';
@GenerateMocks([List, Foo])
void main() {}
'''),
},
message: 'The "classes" argument has unknown types',
);
});
test('throws when GenerateMocks references a non-type', () async {
expectBuilderThrows(
assets: {
...annotationsAsset,
'foo|lib/foo.dart': dedent(r'''
class Foo {}
'''),
'foo|test/foo_test.dart': dedent('''
// missing foo.dart import.
import 'package:mockito/annotations.dart';
@GenerateMocks([7])
void main() {}
'''),
},
message: 'The "classes" argument includes a non-type: int (7)',
);
});
test('throws when GenerateMocks references a typedef', () async {
expectBuilderThrows(
assets: {
...annotationsAsset,
...simpleTestAsset,
'foo|lib/foo.dart': dedent(r'''
typedef Foo = void Function();
'''),
},
message: 'The "classes" argument includes a typedef: Foo',
);
});
test('throws when GenerateMocks references an enum', () async {
expectBuilderThrows(
assets: {
...annotationsAsset,
...simpleTestAsset,
'foo|lib/foo.dart': dedent(r'''
enum Foo {}
'''),
},
message: 'The "classes" argument includes an enum: Foo',
);
});
}
/// Expect that [testBuilder], given [assets], throws an
/// [InvalidMockitoAnnotationException] with a message containing [message].
void expectBuilderThrows(
{@required Map<String, String> assets, @required String message}) {
expect(
() async => await testBuilder(buildMocks(BuilderOptions({})), assets),
throwsA(TypeMatcher<InvalidMockitoAnnotationException>()
.having((e) => e.message, 'message', contains(message))));
}
/// Dedent [input], so that each line is shifted to the left, so that the first
/// line is at the 0 column.
String dedent(String input) {
final indentMatch = RegExp(r'^(\s*)').firstMatch(input);
final indent = ''.padRight(indentMatch.group(1).length);
return input.splitMapJoin('\n',
onNonMatch: (s) => s.replaceFirst(RegExp('^$indent'), ''));
}