OLD | NEW |
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 /** | 5 /** |
6 * The error formatter for mocking is a bit different from the default one | 6 * The error formatter for mocking is a bit different from the default one |
7 * for unit testing; instead of the third argument being a 'reason' | 7 * for unit testing; instead of the third argument being a 'reason' |
8 * it is instead a [signature] describing the method signature filter | 8 * it is instead a [signature] describing the method signature filter |
9 * that was used to select the logs that were verified. | 9 * that was used to select the logs that were verified. |
10 */ | 10 */ |
11 String _mockingErrorFormatter(actual, Matcher matcher, String signature) { | 11 String _mockingErrorFormatter(actual, Matcher matcher, String signature) { |
12 var description = new StringDescription(); | 12 var description = new StringDescription(); |
13 description.add('Expected ${signature} ').addDescriptionOf(matcher). | 13 description.add('Expected ${signature} ').addDescriptionOf(matcher). |
14 add('\n but: '); | 14 add('\n but: '); |
15 matcher.describeMismatch(actual, description); | 15 matcher.describeMismatch(actual, description).add('.'); |
16 return description.toString(); | 16 return description.toString(); |
17 } | 17 } |
18 | 18 |
19 /** | 19 /** |
20 * The failure handler for the [expect()] calls that occur in [verify()] | 20 * The failure handler for the [expect()] calls that occur in [verify()] |
21 * methods in the mock objects. This calls the real failure handler used | 21 * methods in the mock objects. This calls the real failure handler used |
22 * by the unit test library after formatting the error message with | 22 * by the unit test library after formatting the error message with |
23 * the custom formatter. | 23 * the custom formatter. |
24 */ | 24 */ |
25 class _MockFailureHandler implements FailureHandler { | 25 class _MockFailureHandler implements FailureHandler { |
(...skipping 123 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
149 | 149 |
150 /** | 150 /** |
151 * Given a [method] name and list of [arguments], return true | 151 * Given a [method] name and list of [arguments], return true |
152 * if it matches this [CallMatcher. | 152 * if it matches this [CallMatcher. |
153 */ | 153 */ |
154 bool matches(String method, List arguments) { | 154 bool matches(String method, List arguments) { |
155 if (!nameFilter.matches(method)) { | 155 if (!nameFilter.matches(method)) { |
156 return false; | 156 return false; |
157 } | 157 } |
158 if (arguments.length < argMatchers.length) { | 158 if (arguments.length < argMatchers.length) { |
159 throw new Exception("Less arguments than matchers for $method"); | 159 throw new Exception("Less arguments than matchers for $method."); |
160 } | 160 } |
161 for (var i = 0; i < argMatchers.length; i++) { | 161 for (var i = 0; i < argMatchers.length; i++) { |
162 if (!argMatchers[i].matches(arguments[i])) { | 162 if (!argMatchers[i].matches(arguments[i])) { |
163 return false; | 163 return false; |
164 } | 164 } |
165 } | 165 } |
166 return true; | 166 return true; |
167 } | 167 } |
168 } | 168 } |
169 | 169 |
(...skipping 551 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
721 * real = new Foo(); | 721 * real = new Foo(); |
722 * this.when(callsTo('bar')).alwaysCall(real.bar); | 722 * this.when(callsTo('bar')).alwaysCall(real.bar); |
723 * } | 723 * } |
724 * } | 724 * } |
725 * | 725 * |
726 */ | 726 */ |
727 class Mock { | 727 class Mock { |
728 /** The mock name. Needed if the log is shared; optional otherwise. */ | 728 /** The mock name. Needed if the log is shared; optional otherwise. */ |
729 final String name; | 729 final String name; |
730 | 730 |
731 /** The set of [behavior]s supported. */ | 731 /** The set of [Behavior]s supported. */ |
732 Map<String,Behavior> behaviors; | 732 Map<String,Behavior> _behaviors; |
733 | 733 |
734 /** The [log] of calls made. Only used if [name] is null. */ | 734 /** The [log] of calls made. Only used if [name] is null. */ |
735 LogEntryList log; | 735 LogEntryList log; |
736 | 736 |
737 /** How to handle unknown method calls - swallow or throw. */ | 737 /** How to handle unknown method calls - swallow or throw. */ |
738 final bool throwIfNoBehavior = false; | 738 final bool _throwIfNoBehavior; |
| 739 |
| 740 /** Whether to create an audit log or not. */ |
| 741 bool _logging; |
| 742 |
| 743 bool get logging() => _logging; |
| 744 bool set logging(bool value) { |
| 745 if (value && log == null) { |
| 746 log = new LogEntryList(); |
| 747 } |
| 748 _logging = value; |
| 749 } |
739 | 750 |
740 /** | 751 /** |
741 * Default constructor. Unknown method calls are allowed and logged, | 752 * Default constructor. Unknown method calls are allowed and logged, |
742 * the mock has no name, and has its own log. | 753 * the mock has no name, and has its own log. |
743 */ | 754 */ |
744 Mock() : throwIfNoBehavior = false, name = null { | 755 Mock() : _throwIfNoBehavior = false, log = null, name = null { |
745 log = new LogEntryList(); | 756 logging = true; |
746 behaviors = new Map<String,Behavior>(); | 757 _behaviors = new Map<String,Behavior>(); |
747 } | 758 } |
748 | 759 |
749 /** | 760 /** |
750 * This constructor makes a mock that has a [name] and possibly uses | 761 * This constructor makes a mock that has a [name] and possibly uses |
751 * a shared [log]. If [throwIfNoBehavior] is true, any calls to methods | 762 * a shared [log]. If [throwIfNoBehavior] is true, any calls to methods |
752 * that have no defined behaviors will throw an exception; otherwise they | 763 * that have no defined behaviors will throw an exception; otherwise they |
753 * will be allowed and logged (but will not do anything). | 764 * will be allowed and logged (but will not do anything). |
| 765 * If [enableLogging] is false, no logging will be done initially (whether |
| 766 * or not a [log] is supplied), but [logging] can be set to true later. |
754 */ | 767 */ |
755 Mock.custom([this.name, | 768 Mock.custom([this.name, |
756 this.log, | 769 this.log, |
757 this.throwIfNoBehavior = false]) { | 770 throwIfNoBehavior = false, |
758 if (log == null) { | 771 enableLogging = true]) : _throwIfNoBehavior = throwIfNoBehavior { |
759 log = new LogEntryList(); | 772 logging = enableLogging; |
760 } | 773 _behaviors = new Map<String,Behavior>(); |
761 behaviors = new Map<String,Behavior>(); | |
762 } | 774 } |
763 | 775 |
764 /** | 776 /** |
765 * [when] is used to create a new or extend an existing [Behavior]. | 777 * [when] is used to create a new or extend an existing [Behavior]. |
766 * A [CallMatcher] [filter] must be supplied, and the [Behavior]s for | 778 * A [CallMatcher] [filter] must be supplied, and the [Behavior]s for |
767 * that signature are returned (being created first if needed). | 779 * that signature are returned (being created first if needed). |
768 * | 780 * |
769 * Typical use case: | 781 * Typical use case: |
770 * | 782 * |
771 * mock.when(callsTo(...)).alwaysReturn(...); | 783 * mock.when(callsTo(...)).alwaysReturn(...); |
772 */ | 784 */ |
773 Behavior when(CallMatcher logFilter) { | 785 Behavior when(CallMatcher logFilter) { |
774 String key = logFilter.toString(); | 786 String key = logFilter.toString(); |
775 if (!behaviors.containsKey(key)) { | 787 if (!_behaviors.containsKey(key)) { |
776 Behavior b = new Behavior(logFilter); | 788 Behavior b = new Behavior(logFilter); |
777 behaviors[key] = b; | 789 _behaviors[key] = b; |
778 return b; | 790 return b; |
779 } else { | 791 } else { |
780 return behaviors[key]; | 792 return _behaviors[key]; |
781 } | 793 } |
782 } | 794 } |
783 | 795 |
784 /** | 796 /** |
785 * This is the handler for method calls. We loo through the list | 797 * This is the handler for method calls. We loo through the list |
786 * of [Behavior]s, and find the first match that still has return | 798 * of [Behavior]s, and find the first match that still has return |
787 * values available, and then do the action specified by that | 799 * values available, and then do the action specified by that |
788 * return value. If we find no [Behavior] to apply an exception is | 800 * return value. If we find no [Behavior] to apply an exception is |
789 * thrown. | 801 * thrown. |
790 */ | 802 */ |
791 noSuchMethod(String method, List args) { | 803 noSuchMethod(String method, List args) { |
792 if (method.startsWith('get:')) { | 804 if (method.startsWith('get:')) { |
793 method = 'get ${method.substring(4)}'; | 805 method = 'get ${method.substring(4)}'; |
794 } | 806 } |
795 bool matchedMethodName = false; | 807 bool matchedMethodName = false; |
796 for (String k in behaviors.getKeys()) { | 808 for (String k in _behaviors.getKeys()) { |
797 Behavior b = behaviors[k]; | 809 Behavior b = _behaviors[k]; |
798 if (b.matcher.nameFilter.matches(method)) { | 810 if (b.matcher.nameFilter.matches(method)) { |
799 matchedMethodName = true; | 811 matchedMethodName = true; |
800 } | 812 } |
801 if (b.matches(method, args)) { | 813 if (b.matches(method, args)) { |
802 List actions = b.actions; | 814 List actions = b.actions; |
803 if (actions == null || actions.length == 0) { | 815 if (actions == null || actions.length == 0) { |
804 continue; // No return values left in this Behavior. | 816 continue; // No return values left in this Behavior. |
805 } | 817 } |
806 // Get the first response. | 818 // Get the first response. |
807 Responder response = actions[0]; | 819 Responder response = actions[0]; |
808 // If it is exhausted, remove it from the list. | 820 // If it is exhausted, remove it from the list. |
809 // Note that for endlessly repeating values, we started the count at | 821 // Note that for endlessly repeating values, we started the count at |
810 // 0, so we get a potentially useful value here, which is the | 822 // 0, so we get a potentially useful value here, which is the |
811 // (negation of) the number of times we returned the value. | 823 // (negation of) the number of times we returned the value. |
812 if (--response.count == 0) { | 824 if (--response.count == 0) { |
813 actions.removeRange(0, 1); | 825 actions.removeRange(0, 1); |
814 } | 826 } |
815 // Do the response. | 827 // Do the response. |
816 _Action action = response.action; | 828 _Action action = response.action; |
817 var value = response.value; | 829 var value = response.value; |
818 if (action == _Action.RETURN) { | 830 if (action == _Action.RETURN) { |
819 log.add(new LogEntry(name, method, args, action, value)); | 831 if (_logging) { |
| 832 log.add(new LogEntry(name, method, args, action, value)); |
| 833 } |
820 return value; | 834 return value; |
821 } else if (action == _Action.THROW) { | 835 } else if (action == _Action.THROW) { |
822 log.add(new LogEntry(name, method, args, action, value)); | 836 if (_logging) { |
| 837 log.add(new LogEntry(name, method, args, action, value)); |
| 838 } |
823 throw value; | 839 throw value; |
824 } else if (action == _Action.PROXY) { | 840 } else if (action == _Action.PROXY) { |
825 var rtn; | 841 var rtn; |
826 switch (args.length) { | 842 switch (args.length) { |
827 case 0: | 843 case 0: |
828 rtn = value(); | 844 rtn = value(); |
829 break; | 845 break; |
830 case 1: | 846 case 1: |
831 rtn = value(args[0]); | 847 rtn = value(args[0]); |
832 break; | 848 break; |
(...skipping 24 matching lines...) Expand all Loading... |
857 case 9: | 873 case 9: |
858 rtn = value(args[0], args[1], args[2], args[3], | 874 rtn = value(args[0], args[1], args[2], args[3], |
859 args[4], args[5], args[6], args[7], args[8]); | 875 args[4], args[5], args[6], args[7], args[8]); |
860 break; | 876 break; |
861 case 9: | 877 case 9: |
862 rtn = value(args[0], args[1], args[2], args[3], | 878 rtn = value(args[0], args[1], args[2], args[3], |
863 args[4], args[5], args[6], args[7], args[8], args[9]); | 879 args[4], args[5], args[6], args[7], args[8], args[9]); |
864 break; | 880 break; |
865 default: | 881 default: |
866 throw new Exception( | 882 throw new Exception( |
867 "Cannot proxy calls with more than 10 parameters"); | 883 "Cannot proxy calls with more than 10 parameters."); |
868 } | 884 } |
869 log.add(new LogEntry(name, method, args, action, rtn)); | 885 if (_logging) { |
| 886 log.add(new LogEntry(name, method, args, action, rtn)); |
| 887 } |
870 return rtn; | 888 return rtn; |
871 } | 889 } |
872 } | 890 } |
873 } | 891 } |
874 if (matchedMethodName) { | 892 if (matchedMethodName) { |
875 // User did specify behavior for this method, but all the | 893 // User did specify behavior for this method, but all the |
876 // actions are exhausted. This is considered an error. | 894 // actions are exhausted. This is considered an error. |
877 throw new Exception('No more actions for method ' | 895 throw new Exception('No more actions for method ' |
878 '${_qualifiedName(name, method)}'); | 896 '${_qualifiedName(name, method)}.'); |
879 } else if (throwIfNoBehavior) { | 897 } else if (_throwIfNoBehavior) { |
880 throw new Exception('No behavior specified for method ' | 898 throw new Exception('No behavior specified for method ' |
881 '${_qualifiedName(name, method)}'); | 899 '${_qualifiedName(name, method)}.'); |
882 } | 900 } |
883 // User hasn't specified behavior for this method; we don't throw | 901 // Otherwise user hasn't specified behavior for this method; we don't throw |
884 // so we can underspecify. | 902 // so we can underspecify. |
885 log.add(new LogEntry(name, method, args, _Action.IGNORE)); | 903 if (_logging) { |
| 904 log.add(new LogEntry(name, method, args, _Action.IGNORE)); |
| 905 } |
886 } | 906 } |
887 | 907 |
888 /** [verifyZeroInteractions] returns true if no calls were made */ | 908 /** [verifyZeroInteractions] returns true if no calls were made */ |
889 bool verifyZeroInteractions() => log.logs.length == 0; | 909 bool verifyZeroInteractions() { |
| 910 if (log == null) { |
| 911 // This means we created the mock with logging off and have never turned |
| 912 // it on, so it doesn't make sense to verify behavior on such a mock. |
| 913 throw new |
| 914 Exception("Can't verify behavior when logging was never enabled."); |
| 915 } |
| 916 return log.logs.length == 0; |
| 917 } |
890 | 918 |
891 /** | 919 /** |
892 * [getLogs] extracts all calls from the call log that match the | 920 * [getLogs] extracts all calls from the call log that match the |
893 * [logFilter] [CallMatcher], and returns the matching list of | 921 * [logFilter] [CallMatcher], and returns the matching list of |
894 * [LogEntry]s. If [destructive] is false (the default) the matching | 922 * [LogEntry]s. If [destructive] is false (the default) the matching |
895 * calls are left in the log, else they are removed. Removal allows | 923 * calls are left in the log, else they are removed. Removal allows |
896 * us to verify a set of interactions and then verify that there are | 924 * us to verify a set of interactions and then verify that there are |
897 * no other interactions left. [actionMatcher] can be used to further | 925 * no other interactions left. [actionMatcher] can be used to further |
898 * restrict the returned logs based on the action the mock performed. | 926 * restrict the returned logs based on the action the mock performed. |
899 * | 927 * |
900 * Typical usage: | 928 * Typical usage: |
901 * | 929 * |
902 * getLogs(callsTo(...)).verify(...); | 930 * getLogs(callsTo(...)).verify(...); |
903 */ | 931 */ |
904 LogEntryList getLogs([CallMatcher logFilter, | 932 LogEntryList getLogs([CallMatcher logFilter, |
905 Matcher actionMatcher, | 933 Matcher actionMatcher, |
906 bool destructive = false]) { | 934 bool destructive = false]) { |
907 return log.getMatches(name, logFilter, actionMatcher, destructive); | 935 if (log == null) { |
| 936 // This means we created the mock with logging off and have never turned |
| 937 // it on, so it doesn't make sense to get logs from such a mock. |
| 938 throw new |
| 939 Exception("Can't retrieve logs when logging was never enabled."); |
| 940 } else { |
| 941 return log.getMatches(name, logFilter, actionMatcher, destructive); |
| 942 } |
908 } | 943 } |
909 } | 944 } |
OLD | NEW |