diff --git a/src/main/java/org/mockito/testng/MockitoSettings.java b/src/main/java/org/mockito/testng/MockitoSettings.java
new file mode 100644
index 0000000..50b0a7e
--- /dev/null
+++ b/src/main/java/org/mockito/testng/MockitoSettings.java
@@ -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;
+}
diff --git a/src/main/java/org/mockito/testng/MockitoTestNGListener.java b/src/main/java/org/mockito/testng/MockitoTestNGListener.java
index 1a76cf2..067b6f5 100644
--- a/src/main/java/org/mockito/testng/MockitoTestNGListener.java
+++ b/src/main/java/org/mockito/testng/MockitoTestNGListener.java
@@ -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;
@@ -55,6 +58,19 @@
*
*
*
+ * By default {@link MockitoSession} is started with {@link Strictness#STRICT_STUBS}.
+ * You can change this behavior by adding {@link MockitoSettings} to your test class.
+ *
+ *
+ *
+ * @Listeners(MockitoTestNGListener.class)
+ * @MockitoSettings(strictness = Strictness.WARN)
+ * public class ExampleTest {
+ * ...
+ * }
+ *
+ *
+ *
* MockitoTestNGListener
not working with parallel tests,
* more information https://github.com/mockito/mockito-testng/issues/20
*
@@ -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 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);
}
+ Optional findAnnotation(ITestResult testResult, Class annotationClass) {
+
+ for (Class> clazz = testResult.getTestClass().getRealClass();
+ clazz != Object.class; clazz = clazz.getSuperclass()) {
+ Optional annotation = Optional.ofNullable(clazz.getAnnotation(annotationClass));
+ if (annotation.isPresent()) {
+ return annotation;
+ }
+ }
+
+ return Optional.empty();
+ }
}
diff --git a/src/test/java/org/mockitousage/testng/InjectMocksWithConstructorAndFinalTest.java b/src/test/java/org/mockitousage/testng/InjectMocksWithConstructorAndFinalTest.java
new file mode 100644
index 0000000..ebc1a60
--- /dev/null
+++ b/src/test/java/org/mockitousage/testng/InjectMocksWithConstructorAndFinalTest.java
@@ -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();
+ }
+}
diff --git a/src/test/java/org/mockitousage/testng/StrictStubsTest.java b/src/test/java/org/mockitousage/testng/StrictStubsTest.java
index 80c3b31..6d0df62 100644
--- a/src/test/java/org/mockitousage/testng/StrictStubsTest.java
+++ b/src/test/java/org/mockitousage/testng/StrictStubsTest.java
@@ -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;
@@ -22,6 +23,8 @@
import org.testng.annotations.Test;
@Listeners(MockitoTestNGListener.class)
+// MockitoSettings with default values
+@MockitoSettings
public class StrictStubsTest {
@Mock
diff --git a/src/test/java/org/mockitousage/testng/StrictnessWarnTest.java b/src/test/java/org/mockitousage/testng/StrictnessWarnTest.java
new file mode 100644
index 0000000..8f54ccf
--- /dev/null
+++ b/src/test/java/org/mockitousage/testng/StrictnessWarnTest.java
@@ -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 list;
+
+ @BeforeMethod
+ void setup() {
+ when(list.add("a")).thenReturn(true);
+ }
+
+ @Test
+ void not_used_stub_generate_warn() {
+ //
+ }
+}
diff --git a/version.properties b/version.properties
index 96ff41b..67c6069 100644
--- a/version.properties
+++ b/version.properties
@@ -1 +1 @@
-version=0.1.*
+version=0.2.*