0x01 集成PowerMock
目的
在测试中很多时候需要去mock一个对象或者spy某个对象中的几个方法,这个时候PowerMock是一个比较好的选择。
问题
集成PowerMock的时候因为Junit的Runner只能设置一个,所以不知道该设置PowerMockRunner还是SpringRunner。如果设置了PowerMockRunner,虽然可以使用mock和spy功能,但是无法使用Spring提供的功能。所以纠结在此。
解决办法
@RunWith(PowerMockRunner.class)
@PrepareForTest({ProtocolUtil.class})
@PowerMockIgnore("javax.management.*")
@PowerMockRunnerDelegate(SpringRunner.class)
@SpringBootTest(classes = Main.class)
public class TestCreateRoom {
@Autowired
private RoomController roomController;
@Before
public void before() {
// 禁用ProtocolUtil.forward方法
suppress(MemberModifier.method(ProtocolUtil.class, "forward", RestTemplate.class, Long.class, ForwardProtocol.class));
}
...
}
设置Runner为PowerMockRunner,然后使用PowerMock提供的PowerMockRunnerDelegate,将SpringRunner设置为PowerMockRunner的代理,这样就可以两者的功能都使用到了。
0x02 测试bean的注入问题
目的
因为Spring提供的DI功能,所以在测试的时候,我们可以将注册在ApplicationContext中的Bean对象替换为我们的mock对象,这样也是可以达到mock的效果的。
问题
在替换FeignClient对象的时候,会有这个异常抛出。
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'xxx' available: more than one 'primary' bean found among candidates:
可以看到有多个Primary注释修饰的bean,所以导致无法替换。
解决办法
将该对象的注入修改为从外部传入,在构造外部对象的时候,传入一个mock对象。
0x03 TestConfiguration注解的使用问题
目的
在0x02中叙述的替换注册在ApplicationContext中的bean对象这种情况只会在我们写测试用例的时候遇到。所以我们的Configuration一般只会在生产环境中使用,所以Spring提供了TestConfiguration这个注释。
问题
由于默认的SpringApplication只会扫描src/main/java下的包,所以定义在/src/test/java中的TestConfiguration无法在执行测试用例的时候调到。
解决办法
这个问题需要我们在测试用例中使用ContextConfiguration关联下。
@RunWith(PowerMockRunner.class)
@PrepareForTest({ProtocolUtil.class})
@PowerMockIgnore("javax.management.*")
@PowerMockRunnerDelegate(SpringRunner.class)
@SpringBootTest(classes = RoomApplication.class)
@ContextConfiguration(classes=TestCreateRoomConfiguration.class)
public class TestCreateRoom {
...
}
0x04 java.net.MalformedURLException: unknown protocol: classpath
需求
使用URLResouce去加载在resource目录下的文件。
代码如下:
new UrlResource("classpath:/" + fileName);
问题
通过jar包启动是OK的,但是在测试用例中去加载会抛出java.net.MalformedURLException: unknown protocol: classpath异常。断点调试后发现是在java.net.URL类中找不到对应的URLStreamHandler导致的。 于是在使用jar包启动的时候也断点调试进行对比,发现使用jar包启动的时候会得到ClasspathURLStreamHandler这个URLStreamHandler。发现这个是由TomcatURLStreamHandlerFactory创建的,tomcat容器在启动的时候会向URL类中注册war和classpath两个URLStreamHandler,而在测试用例中启动,因为没有tomcat容器的参与所以导致classpath的URLStreamHandler无法注册。
解决办法
在测试用例中提前将URLStreamHandler注册到URL类中。
static {
// register classpath URLStreamHandler
try {
String className = "org.apache.catalina.webresources.TomcatURLStreamHandlerFactory";
Class<?> cls = Class.forName(className);
Constructor<?> constructor = cls.getDeclaredConstructors()[0];
constructor.setAccessible(true);
constructor.newInstance(true);
} catch (Exception e) {
e.printStackTrace();
}
}
// org.apache.catalina.webresources.TomcatURLStreamHandlerFactory
private TomcatURLStreamHandlerFactory(boolean register) {
// Hide default constructor
// Singleton pattern to ensure there is only one instance of this
// factory
this.registered = register;
if (register) {
URL.setURLStreamHandlerFactory(this);
}
}
后续更新:加载classpath下的文件时,使用Spring提供的ResourceLoader去构造一个ClasspathResource也可以解决这个问题。
0x05 Spring Cloud组件屏蔽
问题
在Spring Cloud测试用例运行的时候,会抛出关于consul的异常。
解决办法
替换src/test/resource中的bootstrap.yml文件中的内容如下:
spring:
application:
name: kof-room
cloud:
bus:
enabled: false
discovery:
enabled: false
consul:
enabled: false
config:
enabled: false
把Spring Cloud的各种组件都禁用,这样在测试用例运行的时候就不会抛异常了。
0x06 BDDMockito
问题
屏蔽某个MockBean的方法
解决办法
@MockBean
private lateinit var protocolClient: ProtocolClient
@Before
fun setUp() {
BDDMockito.willDoNothing().given(protocolClient).asyncSend(
ArgumentMatchers.anyCollection(),
ArgumentMatchers.any(ForwardProtocol::class.java)
)
}
BDDMockito.willDoNothing().given(T).method(ArgumentMatchers.anyMethod())