Some of our tests started to hang after we updated to 2.4.0. We narrowed down error to SpringBootMockResolver

It looks like that bean retrieved by SpringBootMockResolver is never released back to pool. For pooled beans this means that pool will eventually run out and tests freeze.

You can find example here: https://github.com/skorhone/spring-boot-test-mockito-bug

Comment From: wilkinsona

See https://github.com/spring-projects/spring-boot/issues/22416 which introduced the mock resolver.

Comment From: wilkinsona

Thanks for the report. The way that Mockito uses the configured MockResolver means that there's no point at which we can release the target. This means that it isn't safe for us to retrieve a target from a non-static source so I think we'll have to ignore proxies with such a target source.

That can be done with a mock resolver like this:

/*
 * Copyright 2012-2020 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.boot.test.mock.mockito;

import org.mockito.plugins.MockResolver;

import org.springframework.aop.TargetSource;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.support.AopUtils;
import org.springframework.test.util.AopTestUtils;
import org.springframework.util.Assert;

/**
 * A {@link MockResolver} for testing Spring Boot applications with Mockito. Resolves
 * mocks in a manner similar to {@link AopTestUtils#getUltimateTargetObject(Object)},
 * but stopping when a proxy with a {@link TargetSource#isStatic() non-static} {@link TargetSource}
 * is encountered.
 *
 * @author Andy Wilkinson
 * @since 2.4.0
 */
public class SpringBootMockResolver implements MockResolver {

    @Override
    public Object resolve(Object instance) {
        return getUltimateStaticTargetObject(instance);
    }

    @SuppressWarnings("unchecked")
    public static <T> T getUltimateStaticTargetObject(Object candidate) {
        Assert.notNull(candidate, "Candidate must not be null");
        try {
            if (AopUtils.isAopProxy(candidate) && candidate instanceof Advised) {
                TargetSource targetSource = ((Advised) candidate).getTargetSource();
                if (targetSource.isStatic()) {
                    Object target = targetSource.getTarget();
                    if (target != null) {
                        return (T) getUltimateStaticTargetObject(target);
                    }
                }
            }
        }
        catch (Throwable ex) {
            throw new IllegalStateException("Failed to unwrap proxied object", ex);
        }
        return (T) candidate;
    }

}

You can work around the problem by copying the above class into src/test/java, ensuring that it remains in the org.springframework.boot.test.mock.mockito package so that it overwrites Boot's class with the same name.

I've reached out to the Framework team to see if methods that avoid drilling down into non-static sources could be added to AopTestUtils or if we should handle this entirely in Boot.

Comment From: skorhone

Thank you.

We were actually able to circumvent the issue with following changes.

First we removed bean that uses proxyFactory directly:

    @Bean(name = "demoProxy")
    public DemoBean demoProxy(@Qualifier(ID) ProxyFactoryBean federationProxyFactoryBean) {
        return (DemoBean) federationProxyFactoryBean.getObject();
    }

And then modified annotation for proxy factory to include name:

    @Bean(name = "demoProxy")
    @Qualifier(ID)
    public ProxyFactoryBean proxyFactoryBean(@Qualifier(ID) CommonsPool2TargetSource targetSource) {
        ProxyFactoryBean p = new ProxyFactoryBean();
        p.setTargetSource(targetSource);
        p.setProxyTargetClass(true);
        return p;
    }    

I assume code fragment that we earlier used was a copy+paste. One of top matches for "spring boot bean pooling" uses the same pattern.

Comment From: wilkinsona

Removing the bean that uses proxyFactory directly is what avoids the problem. That means that there's no longer a singleton bean that is using a pooled target source for its proxying.

Comment From: wilkinsona

Thinking about this some more, I think the problem is due to user error. A singleton bean that uses a pooled target source will permanently borrow an item from the pool which defeats the purpose of the pooling. We already have logic in our code that resets mock to only process singleton beans:

https://github.com/spring-projects/spring-boot/blob/dcae55a8bc8d5cd880354780b5519f61110f4981/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/ResetMocksTestExecutionListener.java#L79

With beans using a pooled target source correctly defined as non-singleton beans, the problem will not occur.

Comment From: skorhone

@wilkinsona I do agree that it doesn't make any sense to create that singleton bean. However, the bean doesn't actually behave like you expected. It doesn't borrow object from pool.

On 2.3.2.RELEASE following code:

    @Test
    void first() throws Exception {
        ExecutorService es = Executors.newFixedThreadPool(5);
        for (int i = 0;i < 100;i++) {
            es.submit(() -> System.out.println(getDemoBean().getId()));
        }
        es.shutdown();
        es.awaitTermination(10, TimeUnit.SECONDS);
    }

Will output:

0e2138d5-aa29-487f-8cb1-33dc24cd3c59
e22032d8-88a0-4337-8c7f-a50c108c44b1
0e2138d5-aa29-487f-8cb1-33dc24cd3c59
e22032d8-88a0-4337-8c7f-a50c108c44b1
0e2138d5-aa29-487f-8cb1-33dc24cd3c59
e22032d8-88a0-4337-8c7f-a50c108c44b1
e22032d8-88a0-4337-8c7f-a50c108c44b1
...

If I'm not mistaken, the singleton bean points to a proxy, that borrows a bean from pool for each method invocation.

Comment From: wilkinsona

You're right. Thanks for the correction. I'm still leaning towards not making a change here as I'd prefer not to add the extra complexity unless it's really needed.