我的目标是为这些单元测试使用内存中的数据库,这些依赖关系列为:
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
runtimeOnly("com.h2database:h2")
因此,存储库实例实际上与数据库交互,而我不只是模拟返回值。
问题是,当我运行单元测试时,服务实例中的存储库实例是null
。
为什么?我是否在单元测试类上缺少一些注释来初始化存储库实例?
这是运行我的单元测试时的控制台输出:
null
java.lang.NullPointerException
at com.my.MyService.findAll(MyService.java:20)
at com.my.MyTest.testMy(MyTest.java:23)
我的单元测试类:
public class MyTest {
@MockBean
MyRepository myRepository;
@Test
void testMy() {
MyService myService = new MyService();
int size = myService.findAll().size();
Assertions.assertEquals(0, size);
}
}
我的服务类别:
@Service
public class MyService {
@Autowired
MyRepository myRepository;
public List<MyEntity> findAll() {
System.out.println(myRepository); // null
return (List<MyEntity>) myRepository.findAll(); // throws NullPointerException
}
@Transactional
public MyEntity create(MyEntity myEntity) {
myRepository.save(myEntity);
return myEntity;
}
}
我的存储库类:
@Repository
public interface MyRepository extends CrudRepository<MyEntity, Long> {
}
我的实体类:
@Entity
public class MyEntity {
@Id
@GeneratedValue
public Long id;
}
为什么?我是否在单元测试类上缺少一些注释来初始化存储库实例?
基本上是的:(
您需要通过用@SpringBootTest
注释您的测试类来初始化Spring上下文
您遇到的另一个问题是手动创建MyService
对象。通过这样做,SpringBoot没有机会为您注入任何Bean。您可以通过简单地在测试类中注入MyService
来解决此问题。你的代码应该是这样的:
@SpringBootTest
public class MyTest {
@Autowired
private MyService myService;
@Test
void testMy() {
int size = myService.findAll().size();
assertEquals(0, size);
}
}
要使用@MockBean
注释,必须使用SpringRunner来运行测试。在测试类的顶部使用@RunWith
注解并通过SpringRunner.class
。
@RunWith(SpringRunner.class)
public class MyTest {
@MockBean
MyRepository myRepository;
@Test
void testMy() {
MyService myService = new MyService();
int size = myService.findAll().size();
Assertions.assertEquals(0, size);
}
}
这里的问题是您的服务实现。当您运行整个应用程序时,使用@Autowired
注入依赖项将起作用,但它不允许您在需要时注入自定义依赖项,测试就是一个很好的例子。
将您的服务实现更改为:
@Service
public class MyService {
private MyRepository myRepository;
public MyService(MyRepository myRepository){
this.myRepository = myRepository;
}
public List<MyEntity> findAll() {
System.out.println(myRepository); // null
return (List<MyEntity>) myRepository.findAll(); // throws NullPointerException
}
@Transactional
public MyEntity create(MyEntity myEntity) {
myRepository.save(myEntity);
return myEntity;
}
}
这个构造函数将由spring调用。然后将您的测试更改为:
public class MyTest {
@Mock
MyRepository myRepository;
@Test
void testMy() {
MyService myService = new MyService(myRepository);
int size = myService.findAll().size();
Assertions.assertEquals(0, size);
}
}
注意,我已经将@MockBean
替换为@Mock
,因为前面的注释用于将mock bean注入到spring上下文中,如果您正在进行单元测试,则不需要它。如果您想要引导spring上下文(我不建议您这样做(,您需要使用@SpringBootTest
或其他一些可用的替代方案来配置测试类。这将把您的测试转换为集成测试。
PD:如果你不给myRepository.findAll()
提供一个mock,这个测试将不起作用。Mockito的默认行为是返回null,但您希望它返回0,因此您需要执行类似given(myRepository.findAll()).willReturn(0)
的操作。
我相信您希望编写一个集成测试。在这里,您可以删除MockBean注释,并简单地自动连接您的存储库。此外,还可以参加The SpringRunner课程。
@RunWith(SpringRunner.class)
public class MyTest {
@Autowired
MyRepository myRepository;
@Autowired
MyService myService
@Test
void testMy() {
int size = myService.findAll().size();
Assertions.assertEquals(0, size);
}
}
这应该工作