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}