1/*
2 * Copyright (c) 2007 Mockito contributors
3 * This program is made available under the terms of the MIT License.
4 */
5package org.mockito.runners;
6
7import java.lang.reflect.InvocationTargetException;
8
9import org.junit.runner.Description;
10import org.junit.runner.Runner;
11import org.junit.runner.manipulation.Filter;
12import org.junit.runner.manipulation.Filterable;
13import org.junit.runner.manipulation.NoTestsRemainException;
14import org.junit.runner.notification.Failure;
15import org.junit.runner.notification.RunListener;
16import org.junit.runner.notification.RunNotifier;
17import org.junit.runners.BlockJUnit4ClassRunner;
18import org.mockito.internal.debugging.WarningsCollector;
19import org.mockito.internal.runners.RunnerFactory;
20import org.mockito.internal.runners.RunnerImpl;
21import org.mockito.internal.util.ConsoleMockitoLogger;
22import org.mockito.internal.util.MockitoLogger;
23
24/**
25 * Uses <b>JUnit 4.5</b> runner {@link BlockJUnit4ClassRunner}.
26 * <p>
27 * Experimental implementation that suppose to improve tdd/testing experience.
28 * Don't hesitate to send feedback to mockito@googlegroups.com
29 * <b>It is very likely it will change in the next version!</b>
30 * <p>
31 * This runner does exactly what {@link MockitoJUnitRunner} does but also
32 * prints warnings that might be useful.
33 * The point is that Mockito should help the tdd developer to quickly figure out if the test fails for the right reason.
34 * Then the developer can implement the functionality.
35 * Also when the test fails it should be easy to figure out why the test fails.
36 * <p>
37 * Sometimes when the test fails, the underlying reason is that stubbed method was called with wrong arguments.
38 * Sometimes it fails because one forgets to stub a method or forgets to call a stubbed method.
39 * All above problems are not immediately obvious.
40 * <p>
41 * One way of approaching this problem is full-blown 'expect' API.
42 * However it means the 'expectations upfront' business which is not in line with core Mockito concepts.
43 * After all, the essence of testing are <b>explicit assertions</b> that are described consistently at the <b>bottom of the test</b> method.
44 * <p>
45 * Here's the experiment: a warning is printed to the standard output if the test fails.
46 * Also, you get a clickabe link to the line of code. You can immediately jump to the place in code where the potential problem is.
47 * <p>
48 * Let's say your test fails on assertion.
49 * Let's say the underlying reason is a stubbed method that was called with different arguments:
50 * <pre class="code"><code class="java">
51 * //test:
52 * Dictionary dictionary = new Dictionary(translator);
53 * when(translator.translate("Mockito")).thenReturn("cool framework");
54 * String translated = dictionary.search("Mockito");
55 * assertEquals("cool framework", translated);
56 *
57 * //code:
58 * public String search(String word) {
59 *     ...
60 *     return translator.translate("oups");
61 *
62 * </code></pre>
63 * On standard output you'll see something like that:
64 * <pre class="code"><code class="java">
65 * [Mockito] Warning - stubbed method called with different arguments.
66 * Stubbed this way:
67 * translator.translate("Mockito");
68 * org.dictionary.SmartDictionaryTest.shouldFindTranslation(SmartDictionaryTest.java:27)
69 *
70 * But called with different arguments:
71 * translator.translate("oups");
72 * org.dictionary.SmartDictionary.search(SmartDictionary.java:15)
73 * </code></pre>
74 * <p>
75 * Note that it is just a warning, not an assertion.
76 * The test fails on assertion because it's the assertion's duty to document what the test stands for and what behavior it proves.
77 * Warnings just makes it quicker to figure out if the test fails for the right reason.
78 * <p>
79 * Note that code links printed to the console are clickable in any decent IDE (e.g. Eclipse).
80 * <p>
81 * So far I identified 2 cases when warnings are printed:
82 * <li>unsued stub</li>
83 * <li>stubbed method but called with different arguments</li>
84 * <p>
85 * <br/>
86 * <p>
87 * Do you think it is useful or not? Drop us an email at mockito@googlegroups.com
88 */
89public class ConsoleSpammingMockitoJUnitRunner extends Runner implements Filterable {
90
91    private final MockitoLogger logger;
92    private RunnerImpl runner;
93
94    public ConsoleSpammingMockitoJUnitRunner(Class<?> klass) throws InvocationTargetException {
95        this(new ConsoleMockitoLogger(), new RunnerFactory().create(klass));
96    }
97
98    ConsoleSpammingMockitoJUnitRunner(MockitoLogger logger, RunnerImpl runnerImpl) {
99        this.runner = runnerImpl;
100        this.logger = logger;
101    }
102
103    @Override
104    public void run(RunNotifier notifier) {
105        RunListener listener = new RunListener() {
106            WarningsCollector warningsCollector;
107
108            @Override
109            public void testStarted(Description description) throws Exception {
110                warningsCollector = new WarningsCollector();
111            }
112
113            @Override public void testFailure(Failure failure) throws Exception {
114                logger.log(warningsCollector.getWarnings());
115            }
116        };
117
118        notifier.addListener(listener);
119
120        runner.run(notifier);
121    }
122
123    @Override
124    public Description getDescription() {
125        return runner.getDescription();
126    }
127
128    public void filter(Filter filter) throws NoTestsRemainException {
129        //filter is required because without it UnrootedTests show up in Eclipse
130        runner.filter(filter);
131    }
132}