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 java.util.function.Supplier; 019import org.junit.runner.notification.RunNotifier; 020import org.junit.runners.BlockJUnit4ClassRunner; 021import org.junit.runners.model.FrameworkMethod; 022import org.junit.runners.model.InitializationError; 023import org.junit.runners.model.Statement; 024import org.junit.runners.model.TestClass; 025import org.mockito.Mock; 026import org.mockito.MockitoAnnotations; 027import org.mockito.internal.runners.util.FrameworkUsageValidator; 028import org.slf4j.Logger; 029import org.slf4j.LoggerFactory; 030 031/** 032 * Alchemy Test Runner Features: 033 * 034 * <ul> 035 * <li> Initializes Mockito {@linkplain Mock @Mocks} 036 * <li> Prints out the testName to the console using {@code System.out.println()} 037 * <li> Can repeat your tests using the {@linkplain Repeat @Repeat} annotation 038 * <li> Initialize generated Data Using {@link GenerateString}, {@link GenerateInteger}, etc.. 039 * 040 * </ul> 041 * 042 * @see <a href="https://github.com/SirWellington/alchemy-test">https://github.com/SirWellington/alchemy-test</a> 043 * @author SirWellington 044 */ 045public class AlchemyTestRunner extends BlockJUnit4ClassRunner 046{ 047 048 private final static Logger LOG = LoggerFactory.getLogger(AlchemyTestRunner.class); 049 050 //If false, we won't initialize Mockito's mocks 051 private boolean shouldInitMocks = true; 052 053 public AlchemyTestRunner(Class<?> klass) throws InitializationError 054 { 055 super(klass); 056 shouldInitMocks = shouldInitMockitoMocks(); 057 } 058 059 @Override 060 protected Statement withBefores(FrameworkMethod method, Object target, Statement statement) 061 { 062 Statement superStatement = super.withBefores(method, target, statement); 063 064 return new Statement() 065 { 066 @Override 067 public void evaluate() throws Throwable 068 { 069 if (shouldInitMocks) 070 { 071 MockitoAnnotations.initMocks(target); 072 } 073 074 TestClassInjectors.populateGeneratedFields(getTestClass(), target); 075 076 superStatement.evaluate(); 077 } 078 }; 079 } 080 081 @Override 082 protected Statement methodBlock(FrameworkMethod method) 083 { 084 int timesToRun = determineTimesToRun(method); 085 Supplier<Statement> statementFactory = () -> super.methodBlock(method); 086 087 return new RepeatStatement(timesToRun, statementFactory, method); 088 } 089 090 @Override 091 public void run(RunNotifier notifier) 092 { 093 if (shouldInitMocks) 094 { 095 //Allow Mockito to do its verification 096 notifier.addListener(new FrameworkUsageValidator(notifier)); 097 } 098 099 super.run(notifier); 100 } 101 102 private int determineTimesToRun(FrameworkMethod method) 103 { 104 105 DontRepeat dontRepeatAnnotation = method.getAnnotation(DontRepeat.class); 106 107 if (dontRepeatAnnotation != null) 108 { 109 return 1; 110 } 111 112 Repeat repeatAnnotationOnMethod = method.getAnnotation(Repeat.class); 113 114 if (repeatAnnotationOnMethod != null) 115 { 116 int value = repeatAnnotationOnMethod.value(); 117 118 if (value <= 0) 119 { 120 LOG.error(method.getName() + " annotated with a negative @Times. Defaulting to 1"); 121 value = 1; 122 } 123 124 return value; 125 } 126 127 Repeat repeatAnnotationOnClass = getTestClass().getAnnotation(Repeat.class); 128 129 if (repeatAnnotationOnClass != null) 130 { 131 int value = repeatAnnotationOnClass.value(); 132 133 if (value <= 0) 134 { 135 LOG.error(getTestClass().getName() + " annotated with a negative @Times. Defaulting to 1"); 136 value = 1; 137 } 138 139 return value; 140 } 141 142 return 1; 143 } 144 145 private boolean shouldInitMockitoMocks() 146 { 147 TestClass testClass = this.getTestClass(); 148 InitMocks initMocks = testClass.getAnnotation(InitMocks.class); 149 150 if (initMocks != null) 151 { 152 return initMocks.value(); 153 } 154 155 return true; 156 } 157 158}