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