从单元测试中使用@Service实例调用方法时,@Repository实例为null



我的目标是为这些单元测试使用内存中的数据库,这些依赖关系列为:

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);
}
}

这应该工作