Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #2648 : Add support for customising strictness via @Mock annotation and MockSettings #2650

Merged
merged 5 commits into from May 26, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/main/java/org/mockito/Mock.java
Expand Up @@ -13,6 +13,7 @@
import java.lang.annotation.Target;

import org.mockito.junit.MockitoJUnitRunner;
import org.mockito.quality.Strictness;
import org.mockito.stubbing.Answer;

/**
Expand Down Expand Up @@ -111,4 +112,12 @@
* @since 2.23.3
*/
boolean lenient() default false;
TimvdLippe marked this conversation as resolved.
Show resolved Hide resolved

/**
* Mock will have custom strictness, see {@link MockSettings#strictness(Strictness)}.
* For examples how to use 'Mock' annotation and parameters see {@link Mock}.
*
* @since 4.5.2
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: @since 4.6.0 since we will include this in the next minor release

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated.

*/
Strictness strictness() default Strictness.STRICT_STUBS;
}
16 changes: 16 additions & 0 deletions src/main/java/org/mockito/MockSettings.java
Expand Up @@ -361,4 +361,20 @@ public interface MockSettings extends Serializable {
* For more information and an elaborate example, see {@link Mockito#lenient()}.
*/
MockSettings lenient();
TimvdLippe marked this conversation as resolved.
Show resolved Hide resolved

/**
* Specifies strictness level for the mock.
* The default strictness level is STRICT_STUBS
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: the default is the default of the runner. If you use the lenient runner, it will be lenient. I suggest to formulate it the following way:

The default strictness level is determined by the rule/runner used. If you are using no rule/runner, the default strictness level is LENIENT.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated.

*
* <pre class="code"><code class="java">
* Foo defaultStrictMock = mock(Foo.class);
* Foo explicitStrictMock = mock(Foo.class, withSettings().strictness(Strictness.STRICT_STUBS));
* Foo lenientMock = mock(Foo.class, withSettings().strictness(Strictness.LENIENT));
* </code></pre>
*
* @param strictness the strictness level to set on mock
* @return settings instance so that you can fluently specify other settings
* @since 4.5.2
*/
MockSettings strictness(Strictness strictness);
}
15 changes: 15 additions & 0 deletions src/main/java/org/mockito/Mockito.java
Expand Up @@ -105,6 +105,7 @@
* <a href="#49">49. New API for mocking object construction (Since 3.5.0)</a><br/>
* <a href="#50">50. Avoiding code generation when restricting mocks to interfaces (Since 3.12.2)</a><br/>
* <a href="#51">51. New API for marking classes as unmockable (Since 4.1.0)</a><br/>
* <a href="#51">52. New strictness attribute for @Mock annotation and <code>MockSettings.strictness()</code> methods (Since 4.5.2)</a><br/>
* </b>
*
* <h3 id="0">0. <a class="meaningful_link" href="#mockito2" name="mockito2">Migrating to Mockito 2</a></h3>
Expand Down Expand Up @@ -1606,6 +1607,20 @@
* For any class/interface you own that is problematic to mock, you can now mark the class with {@link org.mockito.DoNotMock @DoNotMock}. For usage
* of the annotation and how to ship your own (to avoid a compile time dependency on a test artifact), please see its JavaDoc.
* <p>
*
* <h3 id="52">52. <a class="meaningful_link" href="#mockito_strictness" name="mockito_strictness">
* New strictness attribute for @Mock annotation and <code>MockSettings.strictness()</code> methods (Since 4.5.2)</a></h3>
*
* By default Mocks use Strict stubbing and now you can customize the strictness level
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: mocks by default are lenient. I would omit that part altogether and reword it to:

You can now customize the strictness level for a single mock, either using `@Mock` annotation strictness attribute or using `MockSettings.strictness()`. This can be useful if you want all of your mocks to be strict, but one of the mocks to be lenient.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated.

* either using @Mock annotation strictness attribute or using MockSettings.strictness().
*
* <pre class="code"><code class="java">
* &#064;Mock(strictness = Strictness.LENIENT)
* Foo mock;
* // using MockSettings.withSettings()
* Foo mock = Mockito.mock(Foo.class, withSettings().strictness(Strictness.WARN));
* </code></pre>
*
*/
@CheckReturnValue
@SuppressWarnings("unchecked")
Expand Down
Expand Up @@ -45,6 +45,7 @@ public static Object processAnnotationForMock(
if (annotation.stubOnly()) {
mockSettings.stubOnly();
}
mockSettings.strictness(annotation.strictness());
if (annotation.lenient()) {
mockSettings.lenient();
}
Expand Down
Expand Up @@ -12,6 +12,7 @@
import static org.mockito.internal.exceptions.Reporter.extraInterfacesRequiresAtLeastOneInterface;
import static org.mockito.internal.exceptions.Reporter.methodDoesNotAcceptParameter;
import static org.mockito.internal.exceptions.Reporter.requiresAtLeastOneListener;
import static org.mockito.internal.exceptions.Reporter.strictnessDoesNotAcceptNullParameter;
import static org.mockito.internal.util.collections.Sets.newSet;

import java.io.Serializable;
Expand All @@ -33,6 +34,7 @@
import org.mockito.mock.MockCreationSettings;
import org.mockito.mock.MockName;
import org.mockito.mock.SerializableMode;
import org.mockito.quality.Strictness;
import org.mockito.stubbing.Answer;

@SuppressWarnings("unchecked")
Expand Down Expand Up @@ -239,7 +241,16 @@ public <T2> MockCreationSettings<T2> buildStatic(Class<T2> classToMock) {

@Override
public MockSettings lenient() {
this.lenient = true;
this.strictness = Strictness.LENIENT;
return this;
}

@Override
public MockSettings strictness(Strictness strictness) {
this.strictness = strictness;
if (strictness == null) {
throw strictnessDoesNotAcceptNullParameter();
TimvdLippe marked this conversation as resolved.
Show resolved Hide resolved
}
return this;
}

Expand Down
Expand Up @@ -18,6 +18,7 @@
import org.mockito.mock.MockCreationSettings;
import org.mockito.mock.MockName;
import org.mockito.mock.SerializableMode;
import org.mockito.quality.Strictness;
import org.mockito.stubbing.Answer;

public class CreationSettings<T> implements MockCreationSettings<T>, Serializable {
Expand All @@ -44,7 +45,7 @@ public class CreationSettings<T> implements MockCreationSettings<T>, Serializabl
private boolean useConstructor;
private Object outerClassInstance;
private Object[] constructorArgs;
protected boolean lenient;
protected Strictness strictness = Strictness.STRICT_STUBS;

public CreationSettings() {}

Expand All @@ -65,7 +66,7 @@ public CreationSettings(CreationSettings copy) {
this.useConstructor = copy.isUsingConstructor();
this.outerClassInstance = copy.getOuterClassInstance();
this.constructorArgs = copy.getConstructorArgs();
this.lenient = copy.lenient;
this.strictness = copy.strictness;
this.stripAnnotations = copy.stripAnnotations;
}

Expand Down Expand Up @@ -170,6 +171,11 @@ public boolean isStubOnly() {

@Override
public boolean isLenient() {
TimvdLippe marked this conversation as resolved.
Show resolved Hide resolved
return lenient;
return strictness == Strictness.LENIENT;
}

@Override
public Strictness getStrictness() {
return strictness;
}
}
4 changes: 4 additions & 0 deletions src/main/java/org/mockito/internal/exceptions/Reporter.java
Expand Up @@ -964,6 +964,10 @@ public static MockitoException defaultAnswerDoesNotAcceptNullParameter() {
return new MockitoException("defaultAnswer() does not accept null parameter");
}

public static MockitoException strictnessDoesNotAcceptNullParameter() {
return new MockitoException("strictness() does not accept null parameter");
}

public static MockitoException serializableWontWorkForObjectsThatDontImplementSerializable(
Class<?> classToMock) {
return new MockitoException(
Expand Down
Expand Up @@ -38,7 +38,7 @@ public class InvocationContainerImpl implements InvocationContainer, Serializabl

public InvocationContainerImpl(MockCreationSettings mockSettings) {
this.registeredInvocations = createRegisteredInvocations(mockSettings);
this.mockStrictness = mockSettings.isLenient() ? Strictness.LENIENT : null;
this.mockStrictness = mockSettings.getStrictness();
this.doAnswerStyleStubbing = new DoAnswerStyleStubbing();
}

Expand Down
8 changes: 8 additions & 0 deletions src/main/java/org/mockito/mock/MockCreationSettings.java
Expand Up @@ -124,4 +124,12 @@ public interface MockCreationSettings<T> {
* @since 2.20.0
*/
boolean isLenient();

/**
* Sets strictness level for the mock, e.g. having {@link Strictness#STRICT_STUBS} characteristic.
* For more information about using mocks with custom strictness, see {@link MockSettings#strictness(Strictness)}.
*
* @since 4.5.2
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: 4.6.0

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated.

*/
Strictness getStrictness();
}
@@ -0,0 +1,46 @@
/*
* Copyright (c) 2022 Mockito contributors
* This program is made available under the terms of the MIT License.
*/
package org.mockitousage.strictness;

import org.assertj.core.api.Assertions;
import org.assertj.core.api.ThrowableAssert;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.exceptions.misusing.PotentialStubbingProblem;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.mockito.quality.Strictness;
import org.mockitousage.IMethods;

import static org.mockito.Mockito.when;

public class StrictnessMockAnnotationTest {

public @Rule MockitoRule rule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);

@Mock(strictness = Strictness.LENIENT)
IMethods lenientMock;

@Mock IMethods regularMock;

@Test
public void mock_is_lenient() {
when(lenientMock.simpleMethod("1")).thenReturn("1");
when(regularMock.simpleMethod("2")).thenReturn("2");

// then lenient mock does not throw:
TimvdLippe marked this conversation as resolved.
Show resolved Hide resolved
ProductionCode.simpleMethod(lenientMock, "3");

// but regular mock throws:
Assertions.assertThatThrownBy(
new ThrowableAssert.ThrowingCallable() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I think we can use a lambda here, can we not?

() -> ProductionCode.simpleMethod(regularMock, "4")

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated.

public void call() {
ProductionCode.simpleMethod(regularMock, "4");
}
})
.isInstanceOf(PotentialStubbingProblem.class);
}
}
@@ -0,0 +1,62 @@
/*
* Copyright (c) 2022 Mockito contributors
* This program is made available under the terms of the MIT License.
*/
package org.mockitousage.strictness;

import org.assertj.core.api.Assertions;
import org.assertj.core.api.ThrowableAssert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.exceptions.misusing.PotentialStubbingProblem;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.mockito.quality.Strictness;
import org.mockitousage.IMethods;

import static org.mockito.Mockito.*;

public class StrictnessWithSettingsTest {

public @Rule MockitoRule rule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);

IMethods lenientMock;
IMethods regularMock;
IMethods strictMock;

@Before
public void before() {
lenientMock = mock(IMethods.class, withSettings().strictness(Strictness.LENIENT));
regularMock = mock(IMethods.class);
strictMock = mock(IMethods.class, withSettings().strictness(Strictness.STRICT_STUBS));
}

@Test
public void mock_is_lenient() {
when(lenientMock.simpleMethod("1")).thenReturn("1");
when(regularMock.simpleMethod("3")).thenReturn("3");
when(strictMock.simpleMethod("2")).thenReturn("2");

// then lenient mock does not throw:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, let's have 3 separate 3 test cases here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Separated this into multiple tests.

ProductionCode.simpleMethod(lenientMock, "3");

// but regular mock throws:
Assertions.assertThatThrownBy(
new ThrowableAssert.ThrowingCallable() {
public void call() {
ProductionCode.simpleMethod(regularMock, "4");
}
})
.isInstanceOf(PotentialStubbingProblem.class);

// also strict mock throws:
Assertions.assertThatThrownBy(
new ThrowableAssert.ThrowingCallable() {
public void call() {
ProductionCode.simpleMethod(strictMock, "5");
}
})
.isInstanceOf(PotentialStubbingProblem.class);
}
}