写Spring测试用例时遇到的问题解决办法

Posted by CaiJiahe on January 25, 2018

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())