diff --git a/src/main/java/org/mockito/internal/stubbing/InvocationContainerImpl.java b/src/main/java/org/mockito/internal/stubbing/InvocationContainerImpl.java index 849300e61e..3091670fb1 100644 --- a/src/main/java/org/mockito/internal/stubbing/InvocationContainerImpl.java +++ b/src/main/java/org/mockito/internal/stubbing/InvocationContainerImpl.java @@ -163,13 +163,20 @@ public Object invokedMock() { return invocationForStubbing.getInvocation().getMock(); } - public MatchableInvocation getInvocationForStubbing() { - return invocationForStubbing; - } - private RegisteredInvocations createRegisteredInvocations(MockCreationSettings mockSettings) { return mockSettings.isStubOnly() ? new SingleRegisteredInvocation() : new DefaultRegisteredInvocations(); } + + public Answer findStubbedAnswer() { + synchronized (stubbed) { + for (StubbedInvocationMatcher s : stubbed) { + if (invocationForStubbing.matches(s.getInvocation())) { + return s; + } + } + } + return null; + } } diff --git a/src/main/java/org/mockito/internal/stubbing/defaultanswers/ReturnsDeepStubs.java b/src/main/java/org/mockito/internal/stubbing/defaultanswers/ReturnsDeepStubs.java index 0b5a0637a2..097b11b6e2 100644 --- a/src/main/java/org/mockito/internal/stubbing/defaultanswers/ReturnsDeepStubs.java +++ b/src/main/java/org/mockito/internal/stubbing/defaultanswers/ReturnsDeepStubs.java @@ -20,7 +20,6 @@ import org.mockito.invocation.InvocationOnMock; import org.mockito.mock.MockCreationSettings; import org.mockito.stubbing.Answer; -import org.mockito.stubbing.Stubbing; /** * Returning deep stub implementation. @@ -80,12 +79,9 @@ private Object deepStub( throws Throwable { InvocationContainerImpl container = MockUtil.getInvocationContainer(invocation.getMock()); - // matches invocation for verification - // TODO why don't we do container.findAnswer here? - for (Stubbing stubbing : container.getStubbingsDescending()) { - if (container.getInvocationForStubbing().matches(stubbing.getInvocation())) { - return stubbing.answer(invocation); - } + Answer existingAnswer = container.findStubbedAnswer(); + if (existingAnswer != null) { + return existingAnswer.answer(invocation); } // record deep stub answer diff --git a/src/test/java/org/mockito/internal/stubbing/defaultanswers/ReturnsDeepStubsConcurrentTest.java b/src/test/java/org/mockito/internal/stubbing/defaultanswers/ReturnsDeepStubsConcurrentTest.java new file mode 100644 index 0000000000..b3658342ed --- /dev/null +++ b/src/test/java/org/mockito/internal/stubbing/defaultanswers/ReturnsDeepStubsConcurrentTest.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2021 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockito.internal.stubbing.defaultanswers; + +import static org.mockito.Mockito.mock; + +import java.util.List; +import java.util.stream.IntStream; + +import org.junit.Test; +import org.mockito.Answers; + +public class ReturnsDeepStubsConcurrentTest { + + @Test + public void + given_mock_with_returns_deep_stubs__when_called_concurrently__then_does_not_throw_concurrent_modification_exception() { + for (int i = 0; i < 1000; i++) { + Service mock = mock(Service.class, Answers.RETURNS_DEEP_STUBS); + IntStream.range(1, 100).parallel().forEach(index -> mock.doSomething()); + } + } + + interface Service { + List doSomething(); + } +}