英文:
Mocking repository function leads to null pointer exception despite using when and thenReturn
问题
我的ServiceImpl
看起来是这样的:
@Service
public class GuildUsersServiceImpl implements GuildUsersService {
@Autowired
private final GuildUsersRepository guildUsersRepository;
@Autowired
private GuildService guildService;
@Autowired
private UserService userService;
@Autowired
private GuildUserRoleTypeService guildUserRoleTypeService;
@Autowired
public GuildUsersServiceImpl(final GuildUsersRepository guildUsersRepository) {
this.guildUsersRepository = guildUsersRepository;
}
public GuildUsers create(GuildUsers guildUser) {
return this.guildUsersRepository.save(guildUser);
}
}
而我的create
方法的服务测试看起来是这样的:
@RunWith(SpringRunner.class)
public class GuildUsersServiceTest {
@Mock private GuildUsersRepository guildUsersRepository;
@Mock private UserService userService;
@Mock private GuildService guildService;
@Mock private GuildUserRoleTypeService guildUserRoleTypeService;
@InjectMocks private GuildUsersServiceImpl guildUsersService;
@Before
public void setUp(){
MockitoAnnotations.initMocks(this);
}
@Test
public void createTest() {
GuildUsers guildUsers = mockGuildUsers();
when(guildUsersRepository.save(guildUsers)).thenReturn(guildUsers);
GuildUsers dbGuildUsers = guildUsersService.create(guildUsers);
assertEquals(dbGuildUsers.getId(),guildUsers.getId());
}
}
我正在使用jUnit4
。尽管在测试中使用了when(guildUsersRepository.save(guildUsers)).thenReturn(guildUsers);
,但我遇到了一个空指针异常,错误跟踪如下:
java.lang.NullPointerException: Cannot invoke "com.project.entities.domain.GuildUsers.getId()" because "dbGuildUsers" is null
我怀疑在如何模拟@Autowired
类方面出现了问题。在测试类中模拟类时,需要做出哪些不同的操作?
同时,我希望以下测试用例中的服务也能正常工作:
@Test
public void createTest1() {
GuildUsers guildUsers = mockGuildUsers();
GuildUsersWithoutGuildIdRequestDTO guildUsersWithoutGuildIdRequestDTO = new GuildUsersWithoutGuildIdRequestDTO();
guildUsersWithoutGuildIdRequestDTO.setUserId(guildUsers.getUser().getId());
guildUsersWithoutGuildIdRequestDTO.setGuildUserRoleTypeId(guildUsers.getGuildUserRoleType().getId());
when(guildService.get(guildUsers.getGuild().getId())).thenReturn(Optional.of(guildUsers.getGuild()));
when(guildRepository.findById(any())).thenReturn(Optional.of(guildUsers.getGuild()));
when(userService.get(guildUsers.getUser().getId())).thenReturn(Optional.of(guildUsers.getUser()));
when(guildUsersRepository.findById(guildUsers.getId())).thenReturn(null);
when(guildUserRoleTypeService.get(guildUsers.getGuildUserRoleType().getId())).thenReturn(Optional.of(guildUsers.getGuildUserRoleType()));
when(guildUsersRepository.save(any())).thenReturn(guildUsers);
GuildUsersResponseDTO dbGuildUsersResponseDTO =
guildUsersService.create(guildUsers.getGuild().getId(), guildUsersWithoutGuildIdRequestDTO);
assertEquals(dbGuildUsersResponseDTO.getGuildUserRoleType(), guildUsers.getGuildUserRoleType());
}
英文:
My ServiceImpl
looks like this:
@Service
public class GuildUsersServiceImpl implements GuildUsersService {
@Autowired
private final GuildUsersRepository guildUsersRepository;
@Autowired
private GuildService guildService;
@Autowired
private UserService userService;
@Autowired
private GuildUserRoleTypeService guildUserRoleTypeService;
@Autowired
public GuildUsersServiceImpl(final GuildUsersRepository guildUsersRepository) {
this.guildUsersRepository = guildUsersRepository;
}
public GuildUsers create(GuildUsers guildUser) {
return this.guildUsersRepository.save(guildUser);
}
}
And my service test for create
looks like this:
@RunWith(SpringRunner.class)
public class GuildUsersServiceTest {
@Mock private GuildUsersRepository guildUsersRepository;
@Mock private UserService userService;
@Mock private GuildService guildService;
@Mock private GuildUserRoleTypeService guildUserRoleTypeService;
@InjectMocks private GuildUsersServiceImpl guildUsersService;
@Before
public void setUp(){
MockitoAnnotations.initMocks(this);
}
@Test
public void createTest() {
GuildUsers guildUsers = mockGuildUsers();
when(guildUsersRepository.save(guildUsers)).thenReturn(guildUsers);
GuildUsers dbGuildUsers = guildUsersService.create(guildUsers);
assertEquals(dbGuildUsers.getId(),guildUsers.getId());
}
}
I am using jUnit4
. Despite using when(guildUsersRepository.save(guildUsers)).thenReturn(guildUsers);
in the test, I run into a Null Pointer Exception with the following trace:
java.lang.NullPointerException: Cannot invoke "com.project.entities.domain.GuildUsers.getId()" because "dbGuildUsers" is null
I suspect something is wrong with how I have mocked the @Autowired
classes. What needs to be done differently while mocking classes in the Test class ?
At the same time I want the services in the following test case to work as well :
@Test
public void createTest1() {
GuildUsers guildUsers = mockGuildUsers();
GuildUsersWithoutGuildIdRequestDTO guildUsersWithoutGuildIdRequestDTO = new GuildUsersWithoutGuildIdRequestDTO();
guildUsersWithoutGuildIdRequestDTO.setUserId(guildUsers.getUser().getId());
guildUsersWithoutGuildIdRequestDTO.setGuildUserRoleTypeId(guildUsers.getGuildUserRoleType().getId());
when(guildService.get(guildUsers.getGuild().getId())).thenReturn(Optional.of(guildUsers.getGuild()));
when(guildRepository.findById(any())).thenReturn(Optional.of(guildUsers.getGuild()));
when(userService.get(guildUsers.getUser().getId())).thenReturn(Optional.of(guildUsers.getUser()));
when(guildUsersRepository.findById(guildUsers.getId())).thenReturn(null);
when(guildUserRoleTypeService.get(guildUsers.getGuildUserRoleType().getId())).thenReturn(Optional.of(guildUsers.getGuildUserRoleType()));
when(guildUsersRepository.save(any())).thenReturn(guildUsers);
GuildUsersResponseDTO dbGuildUsersResponseDTO =
guildUsersService.create(guildUsers.getGuild().getId(),guildUsersWithoutGuildIdRequestDTO);
assertEquals(dbGuildUsersResponseDTO.getGuildUserRoleType(),guildUsers.getGuildUserRoleType());
}
答案1
得分: 2
我不知道为什么您同时进行了字段注入和构造函数注入。您应该首先删除其中一个,例如:
@Service
public class GuildUsersServiceImpl implements GuildUsersService {
//@Autowired - 已删除
private final GuildUsersRepository guildUsersRepository;
@Autowired
private GuildService guildService;
@Autowired
private UserService userService;
@Autowired
private GuildUserRoleTypeService guildUserRoleTypeService;
//@Autowired - 已删除
public GuildUsersServiceImpl(final GuildUsersRepository guildUsersRepository) {
this.guildUsersRepository = guildUsersRepository;
}
public GuildUsers create(GuildUsers guildUser) {
return this.guildUsersRepository.save(guildUser);
}
}
或者删除构造函数以添加:
@Autowired
private final GuildUsersRepository guildUsersRepository;
英文:
I don't know why you did field injection as well as constructor injection at the same time. You should remove one of them first, for instance:
@Service
public class GuildUsersServiceImpl implements GuildUsersService {
//@Autowired - removed
private final GuildUsersRepository guildUsersRepository;
@Autowired
private GuildService guildService;
@Autowired
private UserService userService;
@Autowired
private GuildUserRoleTypeService guildUserRoleTypeService;
//@Autowired - removed
public GuildUsersServiceImpl(final GuildUsersRepository guildUsersRepository) {
this.guildUsersRepository = guildUsersRepository;
}
public GuildUsers create(GuildUsers guildUser) {
return this.guildUsersRepository.save(guildUser);
}
}
OR remove the constructor to add:
@Autowired
private final GuildUsersRepository guildUsersRepository;```
</details>
# 答案2
**得分**: 0
InjectMock文档明确表示:“Mockito只会尝试通过构造函数注入、setter注入或按顺序和如下所述的属性注入来注入模拟。”
在您的情况下,显然它选择了属性注入,因此guildUserRoleTypeService保持未初始化状态。正如上面指出的,混合使用属性注入和构造函数注入并不是一个好主意。
为了改进GuildUsersServiceImpl的总体设计和可测试性,我建议坚持使用构造函数注入。这样您可以:
a)将所有字段声明为private final,这样一旦对象创建,它们就会被设置,避免了竞态条件和空指针异常。
b)在测试中使用适当的构造函数初始化GuildUsersServiceImpl,而不是使用InjectMocks。
<details>
<summary>英文:</summary>
InjectMock documentation clearly says: "Mockito will try to inject mocks only either by constructor injection, setter injection, or property injection in order and as described below."
In your case, it apparently chooses property injection, hence guildUserRoleTypeService remains uninitialised. Mixing property and constructor injection is not a good idea as pointed above.
To improve the general design and testability of your GuildUsersServiceImpl, I would suggest to stick with constructor injection. This way you can:
a) Declare all the fields as private final, so they will always be set once the object is created, avoiding race conditions and NPEs.
b) Initialise GuildUsersServiceImpl in the test using a proper constructor rather than InjectMocks.
</details>
# 答案3
**得分**: 0
把这个留在这里,为了其他也在浪费一个下午尝试让存储库返回他们配置的内容的人。最终我需要做的是,与其用`@Mock`注解存储库,我必须使用`@MockBean`注解,以使Mockito/Spring组合正常工作。
根据`@MockBean`的javadoc:
> 当在字段上使用@MockBean时,除了在应用程序上下文中注册外,该模拟还将被注入到该字段中。
<details>
<summary>英文:</summary>
Leaving this here for other people that also wasted an afternoon on trying to get a repository to return what they configured. What ended up being the thing I had to do is rather than annotating the repository with `@Mock` I had to use the `@MockBean` annotation for the Mockito/Spring combination to work properly.
As per javadoc of `@MockBean`:
> When @MockBean is used on a field, as well as being registered in the application context, the mock will also be injected into the field.
</details>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论