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}