001/* 002 * Copyright 2015 SirWellington Tech. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package tech.sirwellington.alchemy.test.junit.runners; 017 018import org.junit.runner.notification.RunNotifier; 019import org.junit.runners.BlockJUnit4ClassRunner; 020import org.junit.runners.model.*; 021import org.mockito.*; 022import org.mockito.internal.junit.UnnecessaryStubbingsReporter; 023import org.mockito.internal.runners.util.FailureDetector; 024import org.slf4j.Logger; 025import org.slf4j.LoggerFactory; 026 027/** 028 * Alchemy Test Runner Features: 029 * <p> 030 * <ul> 031 * <li> Initializes Mockito {@linkplain Mock @Mocks} 032 * <li> Prints out the testName to the console using {@code System.out.println()} 033 * <li> Can repeat your tests using the {@linkplain Repeat @Repeat} annotation 034 * <li> Initialize generated Data Using {@link GenerateString}, {@link GenerateInteger}, etc.. 035 * <p> 036 * </ul> 037 * 038 * @author SirWellington 039 * @see <a href="https://github.com/SirWellington/alchemy-test">https://github.com/SirWellington/alchemy-test</a> 040 */ 041public class AlchemyTestRunner extends BlockJUnit4ClassRunner 042{ 043 044 private final static Logger LOG = LoggerFactory.getLogger(AlchemyTestRunner.class); 045 046 //If false, we won't initialize Mockito's mocks 047 private boolean shouldInitMocks = true; 048 049 public AlchemyTestRunner(Class<?> klass) throws InitializationError 050 { 051 super(klass); 052 shouldInitMocks = shouldInitMockitoMocks(); 053 } 054 055 @Override 056 protected Statement withBefores(FrameworkMethod method, final Object target, Statement statement) 057 { 058 final Statement superStatement = super.withBefores(method, target, statement); 059 060 return new Statement() 061 { 062 @Override 063 public void evaluate() throws Throwable 064 { 065 if (shouldInitMocks) 066 { 067 MockitoAnnotations.initMocks(target); 068 } 069 070 TestClassInjectors.populateGeneratedFields(getTestClass(), target); 071 072 superStatement.evaluate(); 073 } 074 }; 075 } 076 077 @Override 078 protected Statement methodBlock(final FrameworkMethod method) 079 { 080 int timesToRun = determineTimesToRun(method); 081 082 Provider<Statement> statementFactory = new Provider<Statement>() 083 { 084 @Override 085 public Statement get() 086 { 087 return AlchemyTestRunner.super.methodBlock(method); 088 } 089 }; 090 091 return new RepeatStatement(timesToRun, statementFactory, method); 092 } 093 094 @Override 095 public void run(RunNotifier notifier) 096 { 097 if (shouldInitMocks) 098 { 099 //Allow Mockito to do its verification 100 UnnecessaryStubbingsReporter reporter = new UnnecessaryStubbingsReporter(); 101 FailureDetector listener = new FailureDetector(); 102 103 Mockito.framework().addListener(reporter); 104 105 try 106 { 107 notifier.addListener(listener); 108 } 109 finally 110 { 111 Mockito.framework().removeListener(reporter); 112 } 113 } 114 115 super.run(notifier); 116 } 117 118 private int determineTimesToRun(FrameworkMethod method) 119 { 120 121 DontRepeat dontRepeatAnnotation = method.getAnnotation(DontRepeat.class); 122 123 if (dontRepeatAnnotation != null) 124 { 125 return 1; 126 } 127 128 Repeat repeatAnnotationOnMethod = method.getAnnotation(Repeat.class); 129 130 if (repeatAnnotationOnMethod != null) 131 { 132 int value = repeatAnnotationOnMethod.value(); 133 134 if (value <= 0) 135 { 136 LOG.error(method.getName() + " annotated with a negative @Times. Defaulting to 1"); 137 value = 1; 138 } 139 140 return value; 141 } 142 143 Repeat repeatAnnotationOnClass = getTestClass().getAnnotation(Repeat.class); 144 145 if (repeatAnnotationOnClass != null) 146 { 147 int value = repeatAnnotationOnClass.value(); 148 149 if (value <= 0) 150 { 151 LOG.error(getTestClass().getName() + " annotated with a negative @Times. Defaulting to 1"); 152 value = 1; 153 } 154 155 return value; 156 } 157 158 return 1; 159 } 160 161 private boolean shouldInitMockitoMocks() 162 { 163 TestClass testClass = this.getTestClass(); 164 InitMocks initMocks = testClass.getAnnotation(InitMocks.class); 165 166 if (initMocks != null) 167 { 168 return initMocks.value(); 169 } 170 171 return true; 172 } 173 174}