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}