alibaba / testable-mock

换种思路写Mock,让单元测试更简单
https://alibaba.github.io/testable-mock/
MIT License
1.83k stars 310 forks source link

no unique public constructor for [org.elasticsearch.transport.Netty4Plugin] #264

Open MeAdhere opened 2 years ago

MeAdhere commented 2 years ago

项目框架:springboot 1.5.9 测试用例

@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:spring-config.xml") public class XxMapperTest { @Resource private XxMapper xxMapper; @Test public void test_get_xx() { Entity entity=new Entity(); entity.setA(System.currentTimeMillis()); entity.setB(1); xxMapper.insert(entity); Entity queryEntity= xxMapper.getXx(entity.getA(), entity.getB()); logger.error("queryEntity---"+ JSON.toJSONString(queryEntity)); Assert.assertTrue(entity.getA().equals(queryEntity.getA())); Assert.assertTrue(entity.getB().equals(queryEntity.getB())); }

}

说明:项目中有引入es框架

org.springframework.data spring-data-elasticsearch 3.1.19.RELEASE

报错: Caused by: java.lang.IllegalStateException: no unique public constructor for [org.elasticsearch.transport.Netty4Plugin] at org.elasticsearch.plugins.PluginsService.loadPlugin(PluginsService.java:543) at org.elasticsearch.plugins.PluginsService.(PluginsService.java:104) at org.elasticsearch.client.transport.TransportClient.newPluginService(TransportClient.java:105) at org.elasticsearch.client.transport.TransportClient.buildTemplate(TransportClient.java:130) at org.elasticsearch.client.transport.TransportClient.(TransportClient.java:273) at org.elasticsearch.transport.client.PreBuiltTransportClient.(PreBuiltTransportClient.java:128) at org.elasticsearch.transport.client.PreBuiltTransportClient.(PreBuiltTransportClient.java:114) at org.elasticsearch.transport.client.PreBuiltTransportClient.(PreBuiltTransportClient.java:104) at com.jd.paimai.user.rights.search.service.es.esclient.SpringDataEsTransportClient.getObject(SpringDataEsTransportClient.java:86) at com.jd.paimai.user.rights.search.service.es.esclient.SpringDataEsTransportClient.getObject(SpringDataEsTransportClient.java:27) at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.doGetObjectFromFactoryBean(FactoryBeanRegistrySupport.java:168) ... 62 more 后经断点运行发现,走到了testable下的com.alibaba.testable.agent.transformer.TestableClassTransformer#transform 然后这个方法mock了一个org.elasticsearch.transport.Netty4Plugin,同时构造方法也多了一个 public org.elasticsearch.transport.Netty4Plugin(java.lang.Void),加上本身自己的构造器,构造器变成了2个 org.elasticsearch.plugins.PluginsService#loadPlugin方法会对构造器内容做校验,导致异常:

private Plugin loadPlugin(Class<? extends Plugin> pluginClass, Settings settings, Path configPath) { Constructor<?>[] constructors = pluginClass.getConstructors(); if (constructors.length == 0) { throw new IllegalStateException("no public constructor for [" + pluginClass.getName() + "]"); } else if (constructors.length > 1) { throw new IllegalStateException("no unique public constructor for [" + pluginClass.getName() + "]"); } else { Constructor<?> constructor = constructors[0]; if (constructor.getParameterCount() > 2) { throw new IllegalStateException(this.signatureMessage(pluginClass)); } else { Class[] parameterTypes = constructor.getParameterTypes();

            try {
                if (constructor.getParameterCount() == 2 && parameterTypes[0] == Settings.class && parameterTypes[1] == Path.class) {
                    return (Plugin)constructor.newInstance(settings, configPath);
                } else if (constructor.getParameterCount() == 1 && parameterTypes[0] == Settings.class) {
                    return (Plugin)constructor.newInstance(settings);
                } else if (constructor.getParameterCount() == 0) {
                    return (Plugin)constructor.newInstance();
                } else {
                    throw new IllegalStateException(this.signatureMessage(pluginClass));
                }
            } catch (ReflectiveOperationException var8) {
                throw new IllegalStateException("failed to load plugin class [" + pluginClass.getName() + "]", var8);
            }
        }
    }
}
linfan commented 2 years ago

你的分析是正确的。

从原理来说,当omni.constructor.enhance.enable配置开启以后,Testable就会为所有待构造的类自动加上一个参数为java.lang.Void的构造方法,用于避开原类型里可能导致抛异常的默认构造方法。由于这个修改必须在类型首次加载的时候进行,因而Testable无法等到执行OmniConstructor.newInstance()再来按需添加,默认会给所有类都加上,但遇到某些框架不允许类型有多个构造方法的时候就会出问题。

解决办法是在testable.properties配置文件里添加以下内容,显式指定不要处理ElasticSearch插件相关的类型:

omni.constructor.enhance.pkgPrefix.excludes = org.elasticsearch.transport,org.elasticsearch.plugin

具体可参考全局运行参数文档。

这个问题我会近期补充到文档的常见使用问题里,在下个版本里也会增加对ElasticPlugin这种特例情况的适配。

感谢反馈。