blob: 172babeb88ed960a5a02b3535b228ba2963d3689 [file] [log] [blame]
library mockito;
import 'package:matcher/matcher.dart';
import 'dart:mirrors';
bool _whenInProgress = false;
bool _verificationInProgress = false;
_WhenCall _whenCall=null;
List<_VerifyCall> _verifyCalls = [];
_TimeStampProvider _timer = new _TimeStampProvider();
List _capturedArgs = [];
class Mock {
List<RealCall> _realCalls = [];
List<CannedResponse> _responses = [];
String _givenName = null;
int _givenHashCode = null;
var _defaultResponse = ()=>new CannedResponse(null, (_)=>null);
void _setExpected(CannedResponse cannedResponse){
_responses.add(cannedResponse);
}
dynamic noSuchMethod(Invocation invocation){
if(_whenInProgress){
_whenCall = new _WhenCall(this, invocation);
return null;
}else if(_verificationInProgress){
_verifyCalls.add(new _VerifyCall(this, invocation));
return null;
}else{
_realCalls.add(new RealCall(this, invocation));
var cannedResponse = _responses.lastWhere((cr)=>cr.matcher.matches(invocation),
orElse: _defaultResponse);
var response = cannedResponse.response(invocation);
return response;
}
}
int get hashCode => _givenHashCode==null ? 0 : _givenHashCode;
bool operator ==(other) => (_givenHashCode!=null && other is Mock)
? _givenHashCode==other._givenHashCode : identical(this, other);
String toString() => _givenName != null ? _givenName : runtimeType.toString();
}
named(dynamic mock, {String name, int hashCode}) => mock
.._givenName=name.._givenHashCode = hashCode;
reset(var mock){
mock._realCalls.clear();
mock._responses.clear();
}
clearInteractions(var mock){
mock._realCalls.clear();
}
class PostExpectation {
thenReturn(expected){
return _completeWhen((_)=>expected);
}
thenAnswer(Answering answer){
return _completeWhen(answer);
}
_completeWhen(Answering answer) {
_whenCall._setExpected(answer);
var mock = _whenCall.mock;
_whenCall = null;
_whenInProgress=false;
return mock;
}
}
class InvocationMatcher {
Invocation roleInvocation;
InvocationMatcher(this.roleInvocation);
bool matches(Invocation invocation){
var isMatching = _isMethodMatches(invocation) && _isArgumentsMatches(invocation);
if(isMatching){
_captureArguments(invocation);
}
return isMatching;
}
bool _isMethodMatches(Invocation invocation) {
if(invocation.memberName!=roleInvocation.memberName){
return false;
}
if((invocation.isGetter!=roleInvocation.isGetter)
|| (invocation.isSetter!=roleInvocation.isSetter)
|| (invocation.isMethod!=roleInvocation.isMethod)
){
return false;
}
return true;
}
void _captureArguments(Invocation invocation) {
int index = 0;
for(var roleArg in roleInvocation.positionalArguments){
var actArg = invocation.positionalArguments[index];
if(roleArg is _ArgMatcher && roleArg._capture){
_capturedArgs.add(actArg);
}
index++;
}
for(var roleKey in roleInvocation.namedArguments.keys){
var roleArg = roleInvocation.namedArguments[roleKey];
var actArg = invocation.namedArguments[roleKey];
if(roleArg is _ArgMatcher){
if(roleArg is _ArgMatcher && roleArg._capture){
_capturedArgs.add(actArg);
}
}
}
}
bool _isArgumentsMatches(Invocation invocation) {
if(invocation.positionalArguments.length!=roleInvocation.positionalArguments.length){
return false;
}
if(invocation.namedArguments.length!=roleInvocation.namedArguments.length){
return false;
}
int index=0;
for(var roleArg in roleInvocation.positionalArguments){
var actArg = invocation.positionalArguments[index];
if(!isMatchingArg(roleArg, actArg)){
return false;
}
index++;
}
for(var roleKey in roleInvocation.namedArguments.keys){
var roleArg = roleInvocation.namedArguments[roleKey];
var actArg = invocation.namedArguments[roleKey];
if(!isMatchingArg(roleArg, actArg)){
return false;
}
}
return true;
}
bool isMatchingArg(roleArg, actArg) {
if(roleArg is _ArgMatcher){
return roleArg._matcher==null || roleArg._matcher.matches(actArg, null);
// } else if(roleArg is Mock){
// return identical(roleArg, actArg);
}else{
return roleArg == actArg;
}
}
}
class CannedResponse{
InvocationMatcher matcher;
Answering response;
CannedResponse(this.matcher, this.response);
}
class _TimeStampProvider{
int _now = 0;
DateTime now(){
var candidate = new DateTime.now();
if(candidate.millisecondsSinceEpoch<=_now){
candidate = new DateTime.fromMillisecondsSinceEpoch(_now+1);
}
_now = candidate.millisecondsSinceEpoch;
return candidate;
}
}
class RealCall{
DateTime _timeStamp;
final Mock mock;
final Invocation invocation;
bool verified = false;
RealCall(this.mock, this.invocation){
_timeStamp = _timer.now();
}
DateTime get timeStamp => _timeStamp;
String toString(){
var verifiedText = verified ? "[VERIFIED] " : "";
List<String> posArgs = invocation.positionalArguments.map((v)=>v==null?"null":v.toString()).toList();
List<String> mapArgList = invocation.namedArguments.keys.map((key){
return "${MirrorSystem.getName(key)}: ${invocation.namedArguments[key]}";
}).toList(growable: false);
if(mapArgList.isNotEmpty){
posArgs.add("{${mapArgList.join(", ")}}");
}
String args = posArgs.join(", ");
String method = MirrorSystem.getName(invocation.memberName);
if(invocation.isMethod){
method = ".$method($args)";
}else if(invocation.isGetter){
method = ".$method";
}else{
method = ".$method=$args";
}
return "$verifiedText$mock$method";
}
}
class _WhenCall {
Mock mock;
Invocation whenInvocation;
_WhenCall(this.mock, this.whenInvocation);
void _setExpected(Answering answer){
mock._setExpected(new CannedResponse(new InvocationMatcher(whenInvocation), answer));
}
}
class _VerifyCall {
Mock mock;
Invocation verifyInvocation;
List<RealCall> matchingInvocations;
_VerifyCall(this.mock, this.verifyInvocation){
var expectedMatcher = new InvocationMatcher(verifyInvocation);
matchingInvocations = mock._realCalls.where((RealCall recordedInvocation){
return !recordedInvocation.verified && expectedMatcher.matches(recordedInvocation.invocation);}).toList();
}
_findAfter(DateTime dt){
return matchingInvocations.firstWhere((inv)=>!inv.verified && inv.timeStamp.isAfter(dt), orElse: ()=>null);
}
_checkWith(bool never){
if(!never && matchingInvocations.isEmpty){
var otherCallsText = "";
if(mock._realCalls.isNotEmpty){
otherCallsText = " All calls: ";
}
var calls = mock._realCalls.join(", ");
fail("No matching calls.$otherCallsText$calls");
}
if(never && matchingInvocations.isNotEmpty){
var calls = mock._realCalls.join(", ");
fail("Unexpected calls. All calls: $calls");
}
matchingInvocations.forEach((inv){inv.verified=true;});
}
}
class _ArgMatcher{
Matcher _matcher;
bool _capture;
_ArgMatcher(this._matcher, this._capture);
}
get any => new _ArgMatcher(null, false);
get captureAny => new _ArgMatcher(null, true);
captureThat(Matcher matcher) => new _ArgMatcher(matcher, true);
argThat(Matcher matcher) => new _ArgMatcher(matcher, false);
class VerificationResult{
List captured = [];
int callCount;
VerificationResult(this.callCount){
captured = new List.from(_capturedArgs, growable: false);
_capturedArgs.clear();
}
void called(matcher){
expect(callCount, wrapMatcher(matcher), reason: "Unexpected number of calls");
}
}
typedef dynamic Answering(Invocation realInvocation);
typedef VerificationResult Verification(matchingInvocations);
typedef void InOrderVerification(recordedInvocations);
Verification get verifyNever => _makeVerify(true);
Verification get verify => _makeVerify(false);
Verification _makeVerify(bool never) {
if(_verifyCalls.isNotEmpty){
throw new StateError(_verifyCalls.join());
}
_verificationInProgress = true;
return (mock){
_verificationInProgress = false;
if(_verifyCalls.length==1){
_VerifyCall verifyCall = _verifyCalls.removeLast();
var result = new VerificationResult(verifyCall.matchingInvocations.length);
verifyCall._checkWith(never);
return result;
}else{
fail("Used on non-mockito");
}
};
}
InOrderVerification get verifyInOrder {
if(_verifyCalls.isNotEmpty){
throw new StateError(_verifyCalls.join());
}
_verificationInProgress = true;
return (verifyCalls){
_verificationInProgress = false;
DateTime dt = new DateTime.fromMillisecondsSinceEpoch(0);
var tmpVerifyCalls = new List.from(_verifyCalls);
_verifyCalls.clear();
List<RealCall> matchedCalls = [];
for(_VerifyCall verifyCall in tmpVerifyCalls){
RealCall matched = verifyCall._findAfter(dt);
if(matched!=null){
matchedCalls.add(matched);
dt=matched.timeStamp;
}else{
Set<Mock> mocks = tmpVerifyCalls.map((_VerifyCall vc)=>vc.mock).toSet();
List<RealCall> allInvocations = mocks.expand((m)=>m._realCalls).toList(growable: false);
allInvocations.sort((inv1, inv2)=>inv1.timeStamp.compareTo(inv2.timeStamp));
String otherCalls = "";
if(allInvocations.isNotEmpty){
otherCalls = " All calls: ${allInvocations.join(", ")}";
}
fail("Matching call #${tmpVerifyCalls.indexOf(verifyCall)} not found.$otherCalls");
}
}
matchedCalls.forEach((rc){rc.verified=true;});
};
}
verifyNoMoreInteractions(var mock) {
var unverified = mock._realCalls.where((inv)=>!inv.verified).toList();
if(unverified.isNotEmpty){
fail("No more calls expected, but following found: "+unverified.join());
}
}
verifyZeroInteractions(var mock) {
if(mock._realCalls.isNotEmpty){
fail("No interaction expected, but following found: "+mock._realCalls.join());
}
}
typedef PostExpectation Expectation(x);
Expectation get when {
_whenInProgress = true;
return (_){
_whenInProgress = false;
return new PostExpectation();
};
}
logInvocations(List<Mock> mocks){
List<RealCall> allInvocations = mocks.expand((m)=>m._realCalls).toList(growable: false);
allInvocations.sort((inv1, inv2)=>inv1.timeStamp.compareTo(inv2.timeStamp));
allInvocations.forEach((inv){
print(inv.toString());
});
}