Skip to content

Commit

Permalink
Fixed \#1967: Correctly handle mocks with limited life-cycle in liste…
Browse files Browse the repository at this point in the history
…ners.
  • Loading branch information
raphw committed Jul 12, 2020
1 parent 05b39bf commit e706fc2
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 42 deletions.
10 changes: 10 additions & 0 deletions src/main/java/org/mockito/MockedStatic.java
Expand Up @@ -63,6 +63,16 @@ default void verify(Verification verification) {
*/
void verifyNoInteractions();

/**
* Checks if this mock is closed.
*
* @return {@code true} if this mock is closed.
*/
boolean isClosed();

/**
* Releases this static mock and throws a {@link org.mockito.exceptions.base.MockitoException} if closed already.
*/
@Override
void close();

Expand Down
10 changes: 10 additions & 0 deletions src/main/java/org/mockito/internal/MockedStaticImpl.java
Expand Up @@ -157,6 +157,11 @@ public void verifyNoInteractions() {
noInteractions().verify(data);
}

@Override
public boolean isClosed() {
return closed;
}

@Override
public void close() {
assertNotClosed();
Expand All @@ -181,4 +186,9 @@ private void assertNotClosed() {
"is already resolved and cannot longer be used"));
}
}

@Override
public String toString() {
return "static mock for " + control.getType().getTypeName();
}
}
Expand Up @@ -58,21 +58,25 @@ public static Object processAnnotationForMock(
}
}

private static Class<?> inferStaticMock(Type type, String name) {
static Class<?> inferStaticMock(Type type, String name) {
if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
return (Class<?>) parameterizedType.getRawType();
} else {
throw new MockitoException(
join(
"Mockito cannot infer a static mock from a raw type for " + name,
"",
"Instead of @Mock MockedStatic you need to specify a parameterized type",
"For example, if you would like to mock static methods of Sample.class, specify",
"",
"@Mock MockedStatic<Sample>",
"",
"as the type parameter"));
Type[] arguments = parameterizedType.getActualTypeArguments();
if (arguments.length == 1) {
if (arguments[0] instanceof Class<?>) {
return (Class<?>) arguments[0];
}
}
}
throw new MockitoException(
join(
"Mockito cannot infer a static mock from a raw type for " + name,
"",
"Instead of @Mock MockedStatic you need to specify a parameterized type",
"For example, if you would like to mock static methods of Sample.class, specify",
"",
"@Mock MockedStatic<Sample>",
"",
"as the type parameter. If the type is parameterized, it should be specified as raw type."));
}
}
Expand Up @@ -43,7 +43,11 @@ public DefaultMockitoSession(
closeables.add(MockitoAnnotations.openMocks(testClassInstance));
}
} catch (RuntimeException e) {
release();
try {
release();
} catch (Throwable t) {
e.addSuppressed(t);
}

// clean up in case 'openMocks' fails
listener.setListenerDirty();
Expand All @@ -58,38 +62,38 @@ public void setStrictness(Strictness strictness) {

@Override
public void finishMocking() {
release();

finishMocking(null);
}

@Override
public void finishMocking(final Throwable failure) {
release();

// Cleaning up the state, we no longer need the listener hooked up
// The listener implements MockCreationListener and at this point
// we no longer need to listen on mock creation events. We are wrapping up the session
Mockito.framework().removeListener(listener);

// Emit test finished event so that validation such as strict stubbing can take place
listener.testFinished(
new TestFinishedEvent() {
@Override
public Throwable getFailure() {
return failure;
}

@Override
public String getTestName() {
return name;
}
});

// Validate only when there is no test failure to avoid reporting multiple problems
if (failure == null) {
// Finally, validate user's misuse of Mockito framework.
Mockito.validateMockitoUsage();
try {
// Cleaning up the state, we no longer need the listener hooked up
// The listener implements MockCreationListener and at this point
// we no longer need to listen on mock creation events. We are wrapping up the session
Mockito.framework().removeListener(listener);

// Emit test finished event so that validation such as strict stubbing can take place
listener.testFinished(
new TestFinishedEvent() {
@Override
public Throwable getFailure() {
return failure;
}

@Override
public String getTestName() {
return name;
}
});

// Validate only when there is no test failure to avoid reporting multiple problems
if (failure == null) {
// Finally, validate user's misuse of Mockito framework.
Mockito.validateMockitoUsage();
}
} finally {
release();
}
}

Expand All @@ -98,7 +102,7 @@ private void release() {
try {
closeable.close();
} catch (Exception e) {
throw new MockitoException("Failed to release mocks", e);
throw new MockitoException("Failed to release " + closeable, e);
}
}
}
Expand Down
Expand Up @@ -42,6 +42,12 @@ public static List<Invocation> find(Iterable<?> mocks) {
public static Set<Stubbing> findStubbings(Iterable<?> mocks) {
Set<Stubbing> stubbings = new TreeSet<Stubbing>(new StubbingComparator());
for (Object mock : mocks) {
// TODO due to the limited scope of static mocks they cannot be processed
// it would rather be required to trigger this stubbing control upon releasing
// the static mock.
if (mock instanceof Class<?>) {
continue;
}
Collection<? extends Stubbing> fromSingleMock =
new DefaultMockingDetails(mock).getStubbings();
stubbings.addAll(fromSingleMock);
Expand Down
@@ -0,0 +1,49 @@
/*
* Copyright (c) 2020 Mockito contributors
* This program is made available under the terms of the MIT License.
*/
package org.mockito.internal.configuration;

import java.util.List;

import org.junit.Test;
import org.mockito.MockedStatic;
import org.mockito.exceptions.base.MockitoException;

import static org.assertj.core.api.Assertions.*;

public class MockAnnotationProcessorTest {

@SuppressWarnings("unused")
private MockedStatic<Void> nonGeneric;

@SuppressWarnings("unused")
private MockedStatic<List<?>> generic;

@SuppressWarnings({"raw", "unused"})
private MockedStatic raw;

@Test
public void testNonGeneric() throws Exception {
Class<?> type =
MockAnnotationProcessor.inferStaticMock(
MockAnnotationProcessorTest.class
.getDeclaredField("nonGeneric")
.getGenericType(),
"nonGeneric");
assertThat(type).isEqualTo(Void.class);
}

@Test(expected = MockitoException.class)
public void testGeneric() throws Exception {
MockAnnotationProcessor.inferStaticMock(
MockAnnotationProcessorTest.class.getDeclaredField("generic").getGenericType(),
"generic");
}

@Test(expected = MockitoException.class)
public void testRaw() throws Exception {
MockAnnotationProcessor.inferStaticMock(
MockAnnotationProcessorTest.class.getDeclaredField("raw").getGenericType(), "raw");
}
}

0 comments on commit e706fc2

Please sign in to comment.