Dart DocumentationmockMock

Mock class

The base class for all mocked objects.

class Mock {
 /** The mock name. Needed if the log is shared; optional otherwise. */
 final String name;

 /** The set of [Behavior]s supported. */
 Map<String,Behavior> _behaviors;

 /** The [log] of calls made. Only used if [name] is null. */
 LogEntryList log;

 /** How to handle unknown method calls - swallow or throw. */
 final bool _throwIfNoBehavior;

 /** Whether to create an audit log or not. */
 bool _logging;

 bool get logging => _logging;
 set logging(bool value) {
   if (value && log == null) {
     log = new LogEntryList();
   }
   _logging = value;
 }

 /**
  * Default constructor. Unknown method calls are allowed and logged,
  * the mock has no name, and has its own log.
  */
 Mock() : _throwIfNoBehavior = false, log = null, name = null {
   logging = true;
   _behaviors = new Map<String,Behavior>();
 }

 /**
  * This constructor makes a mock that has a [name] and possibly uses
  * a shared [log]. If [throwIfNoBehavior] is true, any calls to methods
  * that have no defined behaviors will throw an exception; otherwise they
  * will be allowed and logged (but will not do anything).
  * If [enableLogging] is false, no logging will be done initially (whether
  * or not a [log] is supplied), but [logging] can be set to true later.
  */
 Mock.custom({this.name,
              this.log,
              throwIfNoBehavior: false,
              enableLogging: true}) : _throwIfNoBehavior = throwIfNoBehavior {
   if (log != null && name == null) {
     throw new Exception("Mocks with shared logs must have a name.");
   }
   logging = enableLogging;
   _behaviors = new Map<String,Behavior>();
 }

 /**
  * [when] is used to create a new or extend an existing [Behavior].
  * A [CallMatcher] [filter] must be supplied, and the [Behavior]s for
  * that signature are returned (being created first if needed).
  *
  * Typical use case:
  *
  *     mock.when(callsTo(...)).alwaysReturn(...);
  */
 Behavior when(CallMatcher logFilter) {
   String key = logFilter.toString();
   if (!_behaviors.containsKey(key)) {
     Behavior b = new Behavior(logFilter);
     _behaviors[key] = b;
     return b;
   } else {
     return _behaviors[key];
   }
 }

 /**
  * This is the handler for method calls. We loop through the list
  * of [Behavior]s, and find the first match that still has return
  * values available, and then do the action specified by that
  * return value. If we find no [Behavior] to apply an exception is
  * thrown.
  */
 noSuchMethod(InvocationMirror invocation) {
   var method = invocation.memberName;
   var args = invocation.positionalArguments;
   if (invocation.isGetter) {
     method = 'get $method';
   } else if (invocation.isSetter) {
     method = 'set $method';
     // Remove the trailing '='.
     if (method[method.length-1] == '=') {
       method = method.substring(0, method.length - 1);
     }
   }
   bool matchedMethodName = false;
   MatchState matchState = new MatchState();
   for (String k in _behaviors.keys) {
     Behavior b = _behaviors[k];
     if (b.matcher.nameFilter.matches(method, matchState)) {
       matchedMethodName = true;
     }
     if (b.matches(method, args)) {
       List actions = b.actions;
       if (actions == null || actions.length == 0) {
         continue; // No return values left in this Behavior.
       }
       // Get the first response.
       Responder response = actions[0];
       // If it is exhausted, remove it from the list.
       // Note that for endlessly repeating values, we started the count at
       // 0, so we get a potentially useful value here, which is the
       // (negation of) the number of times we returned the value.
       if (--response.count == 0) {
         actions.removeRange(0, 1);
       }
       // Do the response.
       Action action = response.action;
       var value = response.value;
       if (action == Action.RETURN) {
         if (_logging && b.logging) {
           log.add(new LogEntry(name, method, args, action, value));
         }
         return value;
       } else if (action == Action.THROW) {
         if (_logging && b.logging) {
           log.add(new LogEntry(name, method, args, action, value));
         }
         throw value;
       } else if (action == Action.PROXY) {
         // TODO(gram): Replace all this with:
         //     var rtn = invocation.invokeOn(value);
         // once that is supported.
         var rtn;
         switch (args.length) {
           case 0:
             rtn = value();
             break;
           case 1:
             rtn = value(args[0]);
             break;
           case 2:
             rtn = value(args[0], args[1]);
             break;
           case 3:
             rtn = value(args[0], args[1], args[2]);
             break;
           case 4:
             rtn = value(args[0], args[1], args[2], args[3]);
             break;
           case 5:
             rtn = value(args[0], args[1], args[2], args[3], args[4]);
             break;
           case 6:
             rtn = value(args[0], args[1], args[2], args[3],
                 args[4], args[5]);
             break;
           case 7:
             rtn = value(args[0], args[1], args[2], args[3],
                 args[4], args[5], args[6]);
             break;
           case 8:
             rtn = value(args[0], args[1], args[2], args[3],
                 args[4], args[5], args[6], args[7]);
             break;
           case 9:
             rtn = value(args[0], args[1], args[2], args[3],
                 args[4], args[5], args[6], args[7], args[8]);
             break;
           case 9:
             rtn = value(args[0], args[1], args[2], args[3],
                 args[4], args[5], args[6], args[7], args[8], args[9]);
             break;
           default:
             throw new Exception(
                 "Cannot proxy calls with more than 10 parameters.");
         }
         if (_logging && b.logging) {
           log.add(new LogEntry(name, method, args, action, rtn));
         }
         return rtn;
       }
     }
   }
   if (matchedMethodName) {
     // User did specify behavior for this method, but all the
     // actions are exhausted. This is considered an error.
     throw new Exception('No more actions for method '
         '${_qualifiedName(name, method)}.');
   } else if (_throwIfNoBehavior) {
     throw new Exception('No behavior specified for method '
         '${_qualifiedName(name, method)}.');
   }
   // Otherwise user hasn't specified behavior for this method; we don't throw
   // so we can underspecify.
   if (_logging) {
     log.add(new LogEntry(name, method, args, Action.IGNORE));
   }
 }

 /** [verifyZeroInteractions] returns true if no calls were made */
 bool verifyZeroInteractions() {
   if (log == null) {
     // This means we created the mock with logging off and have never turned
     // it on, so it doesn't make sense to verify behavior on such a mock.
     throw new
         Exception("Can't verify behavior when logging was never enabled.");
   }
   return log.logs.length == 0;
 }

 /**
  * [getLogs] extracts all calls from the call log that match the
  * [logFilter], and returns the matching list of [LogEntry]s. If
  * [destructive] is false (the default) the matching calls are left
  * in the log, else they are removed. Removal allows us to verify a
  * set of interactions and then verify that there are no other
  * interactions left. [actionMatcher] can be used to further
  * restrict the returned logs based on the action the mock performed.
  * [logFilter] can be a [CallMatcher] or a predicate function that
  * takes a [LogEntry] and returns a bool.
  *
  * Typical usage:
  *
  *     getLogs(callsTo(...)).verify(...);
  */
 LogEntryList getLogs([CallMatcher logFilter,
                       Matcher actionMatcher,
                       bool destructive = false]) {
   if (log == null) {
     // This means we created the mock with logging off and have never turned
     // it on, so it doesn't make sense to get logs from such a mock.
     throw new
         Exception("Can't retrieve logs when logging was never enabled.");
   } else {
     return log.getMatches(name, logFilter, actionMatcher, destructive);
   }
 }

 /**
  * Useful shorthand method that creates a [CallMatcher] from its arguments
  * and then calls [getLogs].
  */
 LogEntryList calls(method,
                     [arg0 = _noArg,
                      arg1 = _noArg,
                      arg2 = _noArg,
                      arg3 = _noArg,
                      arg4 = _noArg,
                      arg5 = _noArg,
                      arg6 = _noArg,
                      arg7 = _noArg,
                      arg8 = _noArg,
                      arg9 = _noArg]) =>
     getLogs(callsTo(method, arg0, arg1, arg2, arg3, arg4,
         arg5, arg6, arg7, arg8, arg9));

 /** Clear the behaviors for the Mock. */
 void resetBehavior() => _behaviors.clear();

 /** Clear the logs for the Mock. */
 void clearLogs() {
   if (log != null) {
     if (name == null) { // This log is not shared.
       log.logs.clear();
     } else { // This log may be shared.
       log.logs = log.logs.where((e) => e.mockName != name).toList();
     }
   }
 }

 /** Clear both logs and behavior. */
 void reset() {
   resetBehavior();
   clearLogs();
 }
}

Constructors

new Mock() #

Default constructor. Unknown method calls are allowed and logged, the mock has no name, and has its own log.

Mock() : _throwIfNoBehavior = false, log = null, name = null {
 logging = true;
 _behaviors = new Map<String,Behavior>();
}

new Mock.custom({String name, LogEntryList log, throwIfNoBehavior: false, enableLogging: true}) #

This constructor makes a mock that has a name and possibly uses a shared log. If throwIfNoBehavior is true, any calls to methods that have no defined behaviors will throw an exception; otherwise they will be allowed and logged (but will not do anything). If enableLogging is false, no logging will be done initially (whether or not a log is supplied), but logging can be set to true later.

Mock.custom({this.name,
            this.log,
            throwIfNoBehavior: false,
            enableLogging: true}) : _throwIfNoBehavior = throwIfNoBehavior {
 if (log != null && name == null) {
   throw new Exception("Mocks with shared logs must have a name.");
 }
 logging = enableLogging;
 _behaviors = new Map<String,Behavior>();
}

Properties

LogEntryList log #

The log of calls made. Only used if name is null.

LogEntryList log

bool logging #

bool get logging => _logging;
set logging(bool value) {
 if (value && log == null) {
   log = new LogEntryList();
 }
 _logging = value;
}

final String name #

The mock name. Needed if the log is shared; optional otherwise.

final String name

Methods

LogEntryList calls(method, [arg0 = _noArg, arg1 = _noArg, arg2 = _noArg, arg3 = _noArg, arg4 = _noArg, arg5 = _noArg, arg6 = _noArg, arg7 = _noArg, arg8 = _noArg, arg9 = _noArg]) #

Useful shorthand method that creates a CallMatcher from its arguments and then calls getLogs.

LogEntryList calls(method,
                   [arg0 = _noArg,
                    arg1 = _noArg,
                    arg2 = _noArg,
                    arg3 = _noArg,
                    arg4 = _noArg,
                    arg5 = _noArg,
                    arg6 = _noArg,
                    arg7 = _noArg,
                    arg8 = _noArg,
                    arg9 = _noArg]) =>
   getLogs(callsTo(method, arg0, arg1, arg2, arg3, arg4,
       arg5, arg6, arg7, arg8, arg9));

void clearLogs() #

Clear the logs for the Mock.

void clearLogs() {
 if (log != null) {
   if (name == null) { // This log is not shared.
     log.logs.clear();
   } else { // This log may be shared.
     log.logs = log.logs.where((e) => e.mockName != name).toList();
   }
 }
}

LogEntryList getLogs([CallMatcher logFilter, Matcher actionMatcher, bool destructive = false]) #

getLogs extracts all calls from the call log that match the logFilter, and returns the matching list of LogEntrys. If destructive is false (the default) the matching calls are left in the log, else they are removed. Removal allows us to verify a set of interactions and then verify that there are no other interactions left. actionMatcher can be used to further restrict the returned logs based on the action the mock performed. logFilter can be a CallMatcher or a predicate function that takes a LogEntry and returns a bool.

Typical usage:

getLogs(callsTo(...)).verify(...);
LogEntryList getLogs([CallMatcher logFilter,
                     Matcher actionMatcher,
                     bool destructive = false]) {
 if (log == null) {
   // This means we created the mock with logging off and have never turned
   // it on, so it doesn't make sense to get logs from such a mock.
   throw new
       Exception("Can't retrieve logs when logging was never enabled.");
 } else {
   return log.getMatches(name, logFilter, actionMatcher, destructive);
 }
}

dynamicnoSuchMethod(InvocationMirror invocation) #

This is the handler for method calls. We loop through the list of Behaviors, and find the first match that still has return values available, and then do the action specified by that return value. If we find no Behavior to apply an exception is thrown.

noSuchMethod(InvocationMirror invocation) {
 var method = invocation.memberName;
 var args = invocation.positionalArguments;
 if (invocation.isGetter) {
   method = 'get $method';
 } else if (invocation.isSetter) {
   method = 'set $method';
   // Remove the trailing '='.
   if (method[method.length-1] == '=') {
     method = method.substring(0, method.length - 1);
   }
 }
 bool matchedMethodName = false;
 MatchState matchState = new MatchState();
 for (String k in _behaviors.keys) {
   Behavior b = _behaviors[k];
   if (b.matcher.nameFilter.matches(method, matchState)) {
     matchedMethodName = true;
   }
   if (b.matches(method, args)) {
     List actions = b.actions;
     if (actions == null || actions.length == 0) {
       continue; // No return values left in this Behavior.
     }
     // Get the first response.
     Responder response = actions[0];
     // If it is exhausted, remove it from the list.
     // Note that for endlessly repeating values, we started the count at
     // 0, so we get a potentially useful value here, which is the
     // (negation of) the number of times we returned the value.
     if (--response.count == 0) {
       actions.removeRange(0, 1);
     }
     // Do the response.
     Action action = response.action;
     var value = response.value;
     if (action == Action.RETURN) {
       if (_logging && b.logging) {
         log.add(new LogEntry(name, method, args, action, value));
       }
       return value;
     } else if (action == Action.THROW) {
       if (_logging && b.logging) {
         log.add(new LogEntry(name, method, args, action, value));
       }
       throw value;
     } else if (action == Action.PROXY) {
       // TODO(gram): Replace all this with:
       //     var rtn = invocation.invokeOn(value);
       // once that is supported.
       var rtn;
       switch (args.length) {
         case 0:
           rtn = value();
           break;
         case 1:
           rtn = value(args[0]);
           break;
         case 2:
           rtn = value(args[0], args[1]);
           break;
         case 3:
           rtn = value(args[0], args[1], args[2]);
           break;
         case 4:
           rtn = value(args[0], args[1], args[2], args[3]);
           break;
         case 5:
           rtn = value(args[0], args[1], args[2], args[3], args[4]);
           break;
         case 6:
           rtn = value(args[0], args[1], args[2], args[3],
               args[4], args[5]);
           break;
         case 7:
           rtn = value(args[0], args[1], args[2], args[3],
               args[4], args[5], args[6]);
           break;
         case 8:
           rtn = value(args[0], args[1], args[2], args[3],
               args[4], args[5], args[6], args[7]);
           break;
         case 9:
           rtn = value(args[0], args[1], args[2], args[3],
               args[4], args[5], args[6], args[7], args[8]);
           break;
         case 9:
           rtn = value(args[0], args[1], args[2], args[3],
               args[4], args[5], args[6], args[7], args[8], args[9]);
           break;
         default:
           throw new Exception(
               "Cannot proxy calls with more than 10 parameters.");
       }
       if (_logging && b.logging) {
         log.add(new LogEntry(name, method, args, action, rtn));
       }
       return rtn;
     }
   }
 }
 if (matchedMethodName) {
   // User did specify behavior for this method, but all the
   // actions are exhausted. This is considered an error.
   throw new Exception('No more actions for method '
       '${_qualifiedName(name, method)}.');
 } else if (_throwIfNoBehavior) {
   throw new Exception('No behavior specified for method '
       '${_qualifiedName(name, method)}.');
 }
 // Otherwise user hasn't specified behavior for this method; we don't throw
 // so we can underspecify.
 if (_logging) {
   log.add(new LogEntry(name, method, args, Action.IGNORE));
 }
}

void reset() #

Clear both logs and behavior.

void reset() {
 resetBehavior();
 clearLogs();
}

void resetBehavior() #

Clear the behaviors for the Mock.

void resetBehavior() => _behaviors.clear();

bool verifyZeroInteractions() #

verifyZeroInteractions returns true if no calls were made

bool verifyZeroInteractions() {
 if (log == null) {
   // This means we created the mock with logging off and have never turned
   // it on, so it doesn't make sense to verify behavior on such a mock.
   throw new
       Exception("Can't verify behavior when logging was never enabled.");
 }
 return log.logs.length == 0;
}

Behavior when(CallMatcher logFilter) #

when is used to create a new or extend an existing Behavior. A [CallMatcher] [filter] must be supplied, and the Behaviors for that signature are returned (being created first if needed).

Typical use case:

mock.when(callsTo(...)).alwaysReturn(...);
Behavior when(CallMatcher logFilter) {
 String key = logFilter.toString();
 if (!_behaviors.containsKey(key)) {
   Behavior b = new Behavior(logFilter);
   _behaviors[key] = b;
   return b;
 } else {
   return _behaviors[key];
 }
}