001/* 002 * Stallion Core: A Modern Web Framework 003 * 004 * Copyright (C) 2015 - 2016 Stallion Software LLC. 005 * 006 * This program is free software: you can redistribute it and/or modify it under the terms of the 007 * GNU General Public License as published by the Free Software Foundation, either version 2 of 008 * the License, or (at your option) any later version. This program is distributed in the hope that 009 * it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 010 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public 011 * License for more details. You should have received a copy of the GNU General Public License 012 * along with this program. If not, see <http://www.gnu.org/licenses/gpl-2.0.html>. 013 * 014 * 015 * 016 */ 017 018package io.stallion.testing; 019 020import io.stallion.exceptions.UsageException; 021import io.stallion.services.Log; 022 023import java.lang.reflect.Method; 024import java.text.MessageFormat; 025import java.util.Arrays; 026import java.util.Map; 027 028import static io.stallion.utils.Literals.*; 029 030 031public class Stubbing { 032 private static Map<String, StubHandler> handlersMap = map(); 033 private static Map<String, Integer> callCounts = map(); 034 private static Map<String, StubbingInfo> callerToInfo = map(); 035 036 public static void reset() { 037 handlersMap = map(); 038 callCounts = map(); 039 callerToInfo = map(); 040 } 041 042 public static StubbingInfo stub(Class cls, String method) { 043 return stub(cls, method, new SimpleStubHandler(null)); 044 } 045 046 public static StubbingInfo stub(Class cls, String method, Object result) { 047 return stub(cls, method, new SimpleStubHandler(result)); 048 } 049 050 public static StubbingInfo stub(Class cls, String method, StubHandler handler) { 051 if (handler == null) { 052 throw new UsageException("You cannot stub out with a null IStubHandler handler!"); 053 } 054 Boolean hasMethod = false; 055 for(Method clsMethod: cls.getDeclaredMethods()){ 056 if (clsMethod.getName().equals(method)) { 057 hasMethod = true; 058 break; 059 } 060 } 061 if (!hasMethod) { 062 throw new UsageException("Class " + cls.getName() + " has no method: " + method); 063 } 064 String caller = cls.getName() + "." + method; 065 handlersMap.put(caller, handler); 066 StubbingInfo info = new StubbingInfo(); 067 info.setCaller(caller); 068 callCounts.put(caller, 0); 069 callerToInfo.put(caller, info); 070 return info; 071 } 072 073 public static void checkExecuteStub(Object ...params) throws StubbedOut { 074 Object self = null; 075 Object[] args = params; 076 if (params.length > 0) { 077 self = params[0]; 078 args = Arrays.copyOfRange(params, 1, params.length); 079 } 080 String caller = getCallingClassAndMethod(); 081 Log.info("Checking for stub method for caller: {0}", caller); 082 StubHandler handler = handlersMap.getOrDefault(caller, null); 083 if (handler == null) { 084 return; 085 } 086 callCounts.put(caller, callCounts.get(caller) + 1); 087 Object result = null; 088 try { 089 result = handler.execute(args); 090 } catch(CarryOnWithDefault e) { 091 return; 092 } catch(Exception e) { 093 throw new RuntimeException(e); 094 } 095 throw new StubbedOut(result); 096 } 097 098 private static String getCallingClassAndMethod() { 099 Throwable t = new Throwable(); 100 t.getStackTrace()[2].toString(); 101 String clz = t.getStackTrace()[2].getClassName(); 102 String method = t.getStackTrace()[2].getMethodName(); 103 return clz + "." + method; 104 } 105 106 public static void verifyAndReset() { 107 verify(); 108 reset(); 109 } 110 111 public static void verify() { 112 for(Map.Entry<String, StubbingInfo> entry: callerToInfo.entrySet()) { 113 StubbingInfo info = entry.getValue(); 114 String caller = entry.getKey(); 115 int actual = callCounts.get(entry.getKey()); 116 if (actual < entry.getValue().getMinCount()) { 117 throw new AssertionError(MessageFormat.format( 118 "Expected {0} to be called at least {1} times, was called {2} times", 119 caller, info.getMinCount(), actual)); 120 } 121 if (actual > info.getMaxCount()) { 122 throw new AssertionError(MessageFormat.format( 123 "Expected {0} to be called not more than {1} times, was called {2} times", 124 caller, info.getMaxCount(), actual)); 125 } 126 } 127 } 128 129 public static class StubbedOut extends Exception { 130 public final Object result; 131 132 public StubbedOut(Object result) { 133 this.result = result; 134 } 135 } 136 137 public static class CarryOnWithDefault extends Exception { 138 139 } 140}