Skip to content

Commit

Permalink
fix!: thenAnswer callback invocation
Browse files Browse the repository at this point in the history
  • Loading branch information
felangel committed Dec 30, 2020
1 parent e0fbd5b commit 1783c0b
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 48 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
# 0.0.1-dev.3

- **BREAKING** refactor: rename `positionalArgs` to `positionalArguments`
- **BREAKING** refactor: rename `namedArgs` to `namedArguments`
- fix: `thenAnswer` callback contains correct `Invocation`
- docs: minor documentation updates

# 0.0.1-dev.2

- docs: minor updates to README
Expand Down
103 changes: 56 additions & 47 deletions lib/src/mocktail.dart
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,18 @@ class Mock {
dynamic noSuchMethod(Invocation invocation) {
final _invocationStrict = _Invocation(
memberName: invocation.memberName,
positionalArgs: List.of(invocation.positionalArguments),
namedArgs: Map.of(invocation.namedArguments),
positionalArguments: List.of(invocation.positionalArguments),
namedArguments: Map.of(invocation.namedArguments),
);
final _invocationLax = _Invocation(memberName: invocation.memberName);

if (_stubs.containsKey(_invocationStrict)) {
return _stubs[_invocationStrict]!.result();
final stub = _stubs[_invocationStrict]!.._invocation = invocation;
return stub.result();
}
if (_stubs.containsKey(_invocationLax)) {
return _stubs[_invocationLax]!.result();
final stub = _stubs[_invocationLax]!.._invocation = invocation;
return stub.result();
}
return super.noSuchMethod(invocation);
}
Expand Down Expand Up @@ -113,7 +115,7 @@ void verifyMocks(Object object) {
for (final entry in object._stubs.entries) {
if (entry.value.callCount == 0) {
throw MocktailFailure(
'''${object.runtimeType}.${entry.key.memberName} => ${entry.value._result()} was stubbed but never invoked''',
'''${object.runtimeType}.${entry.key.memberName} => ${entry.value._result(Invocation.getter(const Symbol('entry.key.memberName')))} was stubbed but never invoked''',
);
}
}
Expand All @@ -130,42 +132,46 @@ void reset(Object object) {
class _Invocation {
const _Invocation({
required this.memberName,
this.positionalArgs = const [],
this.namedArgs = const {},
this.positionalArguments = const [],
this.namedArguments = const {},
});

final Symbol memberName;
final Iterable<Object?> positionalArgs;
final Map<Symbol, Object?> namedArgs;
final Iterable<Object?> positionalArguments;
final Map<Symbol, Object?> namedArguments;

@override
bool operator ==(Object o) {
if (identical(this, o)) return true;

return o is _Invocation &&
o.memberName == memberName &&
_listEquals(o.positionalArgs.toList(), positionalArgs.toList()) &&
_mapEquals(o.namedArgs, namedArgs);
_listEquals(
o.positionalArguments.toList(), positionalArguments.toList()) &&
_mapEquals(o.namedArguments, namedArguments);
}

@override
int get hashCode {
final positionalArgsHash = positionalArgs.fold<int>(
final positionalArgumentsHash = positionalArguments.fold<int>(
0, (previous, element) => previous ^ element.hashCode);
final namedArgsHash = namedArgs.entries.fold<int>(0, (previous, element) {
final namedArgumentsHash =
namedArguments.entries.fold<int>(0, (previous, element) {
return previous ^ element.key.hashCode ^ element.value.hashCode;
});
return memberName.hashCode ^ positionalArgsHash ^ namedArgsHash;
return memberName.hashCode ^ positionalArgumentsHash ^ namedArgumentsHash;
}
}

class _Stub {
_Stub(this._result);

final Object? Function() _result;
final Object? Function(Invocation) _result;
late Invocation _invocation;

Object? result() {
_callCount++;
return _result();
return _result(_invocation);
}

int _callCount = 0;
Expand All @@ -191,10 +197,11 @@ class _VerifyArgsCall extends _CallCountCall {
_VerifyArgsCall(
Mock object,
String memberName, {
Iterable<Object?>? positionalArgs,
Map<String, Object?>? namedArgs,
Iterable<Object?>? positionalArguments,
Map<String, Object?>? namedArguments,
}) : super(object, memberName,
positionalArgs: positionalArgs, namedArgs: namedArgs);
positionalArguments: positionalArguments,
namedArguments: namedArguments);

_CallCountCall withArgs({
Iterable<Object?>? positional,
Expand All @@ -203,8 +210,8 @@ class _VerifyArgsCall extends _CallCountCall {
return _CallCountCall(
_object,
_memberName,
positionalArgs: positional,
namedArgs: named,
positionalArguments: positional,
namedArguments: named,
);
}
}
Expand All @@ -213,16 +220,17 @@ class _CallCountCall extends _MockInvocationCall {
_CallCountCall(
Mock object,
String memberName, {
Iterable<Object?>? positionalArgs,
Map<String, Object?>? namedArgs,
Iterable<Object?>? positionalArguments,
Map<String, Object?>? namedArguments,
}) : super(object, memberName,
positionalArgs: positionalArgs, namedArgs: namedArgs);
positionalArguments: positionalArguments,
namedArguments: namedArguments);

void times(int callCount) {
var actualCallCount = 0;

// Lax Invocation Verification (any)
if (_positionalArgs == null && _namedArgs == null) {
if (_positionalArguments == null && _namedArguments == null) {
for (final entry in _object._stubs.entries) {
if (entry.key.memberName == Symbol(_memberName)) {
actualCallCount += entry.value.callCount;
Expand All @@ -248,10 +256,11 @@ class _StubFunction extends _MockInvocationCall {
_StubFunction(
Mock object,
String memberName, {
Iterable<Object?>? positionalArgs,
Map<String, Object?>? namedArgs,
Iterable<Object?>? positionalArguments,
Map<String, Object?>? namedArguments,
}) : super(object, memberName,
positionalArgs: positionalArgs, namedArgs: namedArgs);
positionalArguments: positionalArguments,
namedArguments: namedArguments);

_StubFunction withArgs({
Iterable<Object?>? positional,
Expand All @@ -260,61 +269,61 @@ class _StubFunction extends _MockInvocationCall {
return _StubFunction(
_object,
_memberName,
positionalArgs: positional,
namedArgs: named,
positionalArguments: positional,
namedArguments: named,
);
}

void thenReturn(Object? result) {
_object._stubs[_invocation] = _Stub(() => result);
_object._stubs[_invocation] = _Stub((_) => result);
}

void thenAnswer(Object? Function(_Invocation) callback) {
_object._stubs[_invocation] = _Stub(() => callback(_invocation));
void thenAnswer(Object? Function(Invocation) callback) {
_object._stubs[_invocation] = _Stub(callback);
}

void thenThrow(Object throwable) {
// ignore: only_throw_errors
_object._stubs[_invocation] = _Stub(() => throw throwable);
_object._stubs[_invocation] = _Stub((_) => throw throwable);
}
}

class _MockInvocationCall {
_MockInvocationCall(
this._object,
this._memberName, {
Iterable<Object?>? positionalArgs,
Map<String, Object?>? namedArgs,
}) : _positionalArgs = positionalArgs,
_namedArgs = namedArgs;
Iterable<Object?>? positionalArguments,
Map<String, Object?>? namedArguments,
}) : _positionalArguments = positionalArguments,
_namedArguments = namedArguments;

final Mock _object;
final String _memberName;
final Iterable<Object?>? _positionalArgs;
final Map<String, Object?>? _namedArgs;
final Iterable<Object?>? _positionalArguments;
final Map<String, Object?>? _namedArguments;

_Invocation get _invocation {
if (_positionalArgs == null && _namedArgs == null) {
if (_positionalArguments == null && _namedArguments == null) {
return _Invocation(memberName: Symbol(_memberName));
}
if (_positionalArgs != null && _namedArgs == null) {
if (_positionalArguments != null && _namedArguments == null) {
return _Invocation(
memberName: Symbol(_memberName),
positionalArgs: _positionalArgs!,
positionalArguments: _positionalArguments!,
);
}
if (_positionalArgs == null && _namedArgs != null) {
if (_positionalArguments == null && _namedArguments != null) {
return _Invocation(
memberName: Symbol(_memberName),
namedArgs: _namedArgs!.map<Symbol, Object?>(
namedArguments: _namedArguments!.map<Symbol, Object?>(
(key, value) => MapEntry(Symbol(key), value),
),
);
}
return _Invocation(
memberName: Symbol(_memberName),
positionalArgs: _positionalArgs!,
namedArgs: _namedArgs!.map<Symbol, Object?>(
positionalArguments: _positionalArguments!,
namedArguments: _namedArguments!.map<Symbol, Object?>(
(key, value) => MapEntry(Symbol(key), value),
),
);
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: mocktail
description: A Dart mocking library which simplifies mocking with null safety support and no manual mocks or code generation.
version: 0.0.1-dev.2
version: 0.0.1-dev.3
repository: https://github.com/felangel/mocktail
homepage: https://github.com/felangel/mocktail

Expand Down
15 changes: 15 additions & 0 deletions test/mocktail_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,21 @@ void main() {
.withArgs(positional: [2], named: {'y': 4}).times(1);
});

test('invocation contains correct arguments', () async {
late Iterable<Object?> positionalArguments;
late Map<Symbol, dynamic> namedArguments;
when(foo)
.calls('asyncValueWithNamedAndPositionalArgs')
.thenAnswer((invocation) async {
positionalArguments = invocation.positionalArguments;
namedArguments = invocation.namedArguments;
return 10;
});
expect(await foo.asyncValueWithNamedAndPositionalArgs(1, y: 2), 10);
expect(positionalArguments, equals([1]));
expect(namedArguments, equals({#y: 2}));
});

test('when streamValue', () {
when(foo).calls('streamValue').thenAnswer((_) => Stream.value(42));
expectLater(
Expand Down

0 comments on commit 1783c0b

Please sign in to comment.