项目背景:
正式环境项目上线前跑单元测试发现一直报 NullPointerException
。
项目结构如下:
配置文件如下:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.6.RELEASE</version>
<relativePath/>
</parent>
<properties>
<java.version>11</java.version>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
</plugin>
</plugins>
</build>
完整代码如下:
@RestController
@RequestMapping("/example")
public class ExampleController {
@Autowired
private ExampleManager exampleManager;
public void test() {
exampleManager.test();
}
public void setExampleManager(ExampleManager exampleManager) {
this.exampleManager = exampleManager;
}
}
@Component
public class ExampleManager {
@Autowired
private ImportManager importManager;
public void test() {
importManager.test();
}
}
@Component
public class ImportManager {
public void test() {
}
}
public class MockExampleManager extends ExampleManager {
}
@RunWith(SpringRunner.class)
@SpringBootTest(classes = StartServer.class)
public class GoodTest {
private Logger log = LoggerFactory.getLogger(GoodTest.class);
@Autowired
private ExampleController exampleController;
MockExampleManager mockExampleManager;
@Before
public void before() {
log.info("执行{}.before", this.getClass().getName());
mockExampleManager = new MockExampleManager();
exampleController.setExampleManager(mockExampleManager);
}
@Test
public void test() {
exampleController.test();
}
@RunWith(SpringRunner.class)
@SpringBootTest(classes = StartServer.class)
public class OrderTest {
private Logger log = LoggerFactory.getLogger(OrderTest.class);
@Autowired
private ExampleController exampleController;
MockExampleManager mockExampleManager;
@Before
public void before() {
log.info("执行{}.before", this.getClass().getName());
mockExampleManager = new MockExampleManager();
exampleController.setExampleManager(mockExampleManager);
}
@Test
public void test() {
exampleController.test();
}
}
问题描述:
SpringBoot项目本地打包未报错,测试环境Jenkins打包即报错。
提取关键代码如下:
@Component
public class ExampleManager {
@Autowired
private ImportManager importManager;
public void test() {
importManager.test();
}
}
以上方法的 importManager.test()
在Jenkins上打包时总是会报 NullPointerException
。在本地打包时就不会。
原因分析:
第一步:找到 Jenkins
上报错的类和方法 ExampleManager.test()
,通过本地 Debug 发现单元测试运行到 ExampleManager.test()
方法时,属性 importManager
并不是 Spring 容器中的 ImportManager
类实例,而是 MockExampleManager
类实例。
第二步:查找用到 MockExampleManager
类的地方,发现 OrderTest
类用到了,于是注释了 OrderTest
中 MockExampleManager
类的代码。此时本地打包不再报错,但是 Jenkins
打包依然报错。
猜测到是其他类注入了 ExampleController
并修改了依赖的 ExampleManager
实例。
第三步:注释 GoodTest
中 MockExampleManager
类的代码。此时本地和 Jenkins
打包都不再报错。
查询资料后分析原因应该是不同操作系统下文件排序会有差异,导致两个单元测试类的执行顺序有差异。
又由于两个单元测试类都注入了 ExampleController
实例,这个实例是单例的,一个地方对 ExampleController
实例属性的修改会影响其他地方。
在我本地因为是 OrderTest
先执行,所以 GoodTest
中的 exampleController.setExampleManager(exampleManager)
代码影响不到 OrderTest
。
如下图所示,我本地的运行效果:
解决方案:
找到用 MockExampleManager
类的地方,发现 GoodTest
和 OrderTest
类中的 exampleController.setExampleManager(mockExampleManager)
代码是没有必要的,因此删除,就解决了 NullPointerException
异常。
没有 NullPointerException
异常,在不同的平台打包都不会出错啦。