Skip to content

Commit

Permalink
Merge pull request #27 from mockito/fix-10
Browse files Browse the repository at this point in the history
Possibility to configure strictness level
  • Loading branch information
slawekjaranowski authored Dec 11, 2020
2 parents 8ab741f + 74c4dbb commit 442aac5
Show file tree
Hide file tree
Showing 6 changed files with 210 additions and 29 deletions.
31 changes: 31 additions & 0 deletions src/main/java/org/mockito/testng/MockitoSettings.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright (c) 2020 Mockito contributors
* This program is made available under the terms of the MIT License.
*/
package org.mockito.testng;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.mockito.quality.Strictness;

/**
* Annotation that can configure Mockito settings. Used by {@link MockitoTestNGListener}
*/
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
public @interface MockitoSettings {

/**
* Configure the strictness used in this test.
*
* @return The strictness to configure, by default {@link Strictness#STRICT_STUBS}
*/
Strictness strictness() default Strictness.STRICT_STUBS;
}
88 changes: 60 additions & 28 deletions src/main/java/org/mockito/testng/MockitoTestNGListener.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,19 @@
*/
package org.mockito.testng;

import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Map;
import java.util.Optional;
import java.util.WeakHashMap;
import java.util.stream.Stream;

import org.mockito.Mockito;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
import org.testng.IInvokedMethod;
import org.testng.IInvokedMethodListener;
import org.testng.ITestNGListener;
import org.testng.ITestNGMethod;
import org.testng.ITestResult;
import org.testng.annotations.Listeners;

Expand Down Expand Up @@ -55,6 +58,19 @@
* </code></pre>
*
* <p>
* By default {@link MockitoSession} is started with {@link Strictness#STRICT_STUBS}.
* You can change this behavior by adding {@link MockitoSettings} to your test class.
* </p>
*
* <pre class="code"><code class="java">
* <b>&#064;Listeners(MockitoTestNGListener.class)</b>
* <b>&#064;MockitoSettings(strictness = Strictness.WARN)</b>
* public class ExampleTest {
* ...
* }
* </code></pre>
*
* <p>
* <code>MockitoTestNGListener</code> not working with parallel tests,
* more information https://github.com/mockito/mockito-testng/issues/20
* </p>
Expand All @@ -65,49 +81,65 @@ public class MockitoTestNGListener implements IInvokedMethodListener {

@Override
public void beforeInvocation(IInvokedMethod method, ITestResult testResult) {
if (hasMockitoTestNGListenerInTestHierarchy(testResult)) {
sessions.computeIfAbsent(testResult.getInstance(), testInstance ->
Mockito.mockitoSession()
.initMocks(testInstance)
.strictness(Strictness.STRICT_STUBS)
.startMocking()
if (shouldBeRunBeforeInvocation(method, testResult)) {
sessions.computeIfAbsent(testResult.getInstance(), testInstance -> {

Strictness strictness = findAnnotation(testResult, MockitoSettings.class)
.map(MockitoSettings::strictness).orElse(Strictness.STRICT_STUBS);

return Mockito.mockitoSession()
.initMocks(testInstance)
.strictness(strictness)
.startMocking();
}
);
}
}

@Override
public void afterInvocation(IInvokedMethod method, ITestResult testResult) {
if (hasMockitoTestNGListenerInTestHierarchy(testResult) && method.isTestMethod()) {
if (shouldBeRunAfterInvocation(method, testResult)) {
Optional.ofNullable(sessions.remove(testResult.getInstance()))
.ifPresent(mockitoSession -> mockitoSession.finishMocking(testResult.getThrowable()));
}
}

protected boolean hasMockitoTestNGListenerInTestHierarchy(ITestResult testResult) {
for (Class<?> clazz = testResult.getTestClass().getRealClass(); clazz != Object.class; clazz = clazz.getSuperclass()) {
if (hasMockitoTestNGListener(clazz)) {
return true;
}
}
return false;
private boolean shouldBeRunBeforeInvocation(IInvokedMethod method, ITestResult testResult) {
return !isAfterConfigurationMethod(method) && hasMockitoTestNGListener(testResult);
}

protected boolean hasMockitoTestNGListener(Class<?> clazz) {
Listeners listeners = clazz.getAnnotation(Listeners.class);
if (listeners == null) {
return false;
}
private boolean isAfterConfigurationMethod(IInvokedMethod method) {
ITestNGMethod testMethod = method.getTestMethod();
return testMethod.isAfterClassConfiguration()
|| testMethod.isAfterMethodConfiguration()
|| testMethod.isAfterGroupsConfiguration()
|| testMethod.isAfterTestConfiguration()
|| testMethod.isAfterSuiteConfiguration();
}

for (Class<? extends ITestNGListener> listenerClass : listeners.value()) {
if (listenerClass() == listenerClass) {
return true;
}
}
return false;
private boolean shouldBeRunAfterInvocation(IInvokedMethod method, ITestResult testResult) {
return method.isTestMethod() && hasMockitoTestNGListener(testResult);
}

protected Class<MockitoTestNGListener> listenerClass() {
return MockitoTestNGListener.class;
protected boolean hasMockitoTestNGListener(ITestResult testResult) {

return findAnnotation(testResult, Listeners.class)
.map(Listeners::value)
.map(Arrays::stream)
.orElseGet(Stream::empty)
.anyMatch(listener -> listener == MockitoTestNGListener.class);
}

<A extends Annotation> Optional<A> findAnnotation(ITestResult testResult, Class<A> annotationClass) {

for (Class<?> clazz = testResult.getTestClass().getRealClass();
clazz != Object.class; clazz = clazz.getSuperclass()) {
Optional<A> annotation = Optional.ofNullable(clazz.getAnnotation(annotationClass));
if (annotation.isPresent()) {
return annotation;
}
}

return Optional.empty();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright (c) 2020 Mockito contributors
* This program is made available under the terms of the MIT License.
*/
package org.mockitousage.testng;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import java.util.Random;

import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.testng.MockitoTestNGListener;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;

@Listeners(MockitoTestNGListener.class)
public class InjectMocksWithConstructorAndFinalTest {

interface Client {
int aMethod();
}

static class Service {
final Client client;

public Service(Client client) {
this.client = client;
}

int callClient() {
return client.aMethod();
}
}

@Mock
private Client client;

@InjectMocks
private Service service;

private int lastClientHash = 0;

@AfterMethod
void cleanup() {
// final field will be not assigned for next test
// for new object constructor will be used
service = null;
}

@Test(invocationCount = 4)
void inject_mock_should_be_refreshed() {

// we have correct injected mock
assertThat(client).isNotNull();
assertThat(service.client).isNotNull().isSameAs(client);

// we have new mock
assertThat(lastClientHash).isNotEqualTo(client.hashCode());

// clear mock
assertThat(service.callClient()).isZero();

// make some stub
int i = new Random().nextInt() + 1;
when(client.aMethod()).thenReturn(i);

// and test it
assertThat(service.callClient()).isEqualTo(i);

verify(client, times(2)).aMethod();

// remember last mock hash
lastClientHash = client.hashCode();
}
}
3 changes: 3 additions & 0 deletions src/test/java/org/mockitousage/testng/StrictStubsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import org.mockito.Mock;
import org.mockito.exceptions.misusing.PotentialStubbingProblem;
import org.mockito.exceptions.misusing.UnnecessaryStubbingException;
import org.mockito.testng.MockitoSettings;
import org.mockito.testng.MockitoTestNGListener;
import org.mockitousage.testng.failuretests.HasUnusedStubs;
import org.mockitousage.testng.failuretests.HasUnusedStubsInSetup;
Expand All @@ -22,6 +23,8 @@
import org.testng.annotations.Test;

@Listeners(MockitoTestNGListener.class)
// MockitoSettings with default values
@MockitoSettings
public class StrictStubsTest {

@Mock
Expand Down
35 changes: 35 additions & 0 deletions src/test/java/org/mockitousage/testng/StrictnessWarnTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright (c) 2020 Mockito contributors
* This program is made available under the terms of the MIT License.
*/
package org.mockitousage.testng;

import static org.mockito.Mockito.when;

import java.util.List;

import org.mockito.Mock;
import org.mockito.quality.Strictness;
import org.mockito.testng.MockitoSettings;
import org.mockito.testng.MockitoTestNGListener;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;

@Listeners(MockitoTestNGListener.class)
@MockitoSettings(strictness = Strictness.WARN)
public class StrictnessWarnTest {

@Mock
private List<String> list;

@BeforeMethod
void setup() {
when(list.add("a")).thenReturn(true);
}

@Test
void not_used_stub_generate_warn() {
//
}
}
2 changes: 1 addition & 1 deletion version.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
version=0.1.*
version=0.2.*

0 comments on commit 442aac5

Please sign in to comment.