You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

单元测试模拟继承JpaRepository的接口时触发异常问题排查

问题分析与解决方案

问题背景

测试PersonServiceImpl的createPerson方法时,执行roleRepository.findById(roleId)返回空,触发EntityNotFoundException异常。尝试过使用Mockito Mock和直接@Autowired注入RoleRepository均未解决,调试发现Mockito生成的RoleRepository实例ID与主类中的不同。

相关代码片段

PersonServiceImpl核心代码

package com.personregistrationspring.service;

import com.personregistrationspring.model.Person;
import com.personregistrationspring.model.Name;
import com.personregistrationspring.model.Address;
import com.personregistrationspring.model.Contact;
import com.personregistrationspring.model.dto.PersonDto;
import com.personregistrationspring.model.Role;
import com.personregistrationspring.repository.PersonRepository;
import com.personregistrationspring.repository.RoleRepository;

import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import javax.persistence.EntityNotFoundException;

import java.util.*;


@Service
public class PersonServiceImpl implements PersonService{
    
    private final PersonRepository personRepository;
    private final RoleRepository roleRepository;
    
    @Autowired
    public PersonServiceImpl(PersonRepository personRepository, RoleRepository roleRepository){
        this.personRepository = personRepository;
        this.roleRepository = roleRepository;
    }
    
    @Transactional
    @Override
    public PersonDto createPerson(PersonDto personDto){
        Person person = mapToEntity(personDto);
        
        Set<Integer> roleIds = personDto.getRoleIds();
        
        Set<Role> roles = new HashSet<>();
        
        for(Integer roleId : roleIds){
            Optional<Role> optionalRole = roleRepository.findById(roleId);
            if(optionalRole.isPresent()){
                Role role = optionalRole.get();
                roles.add(role);
            } else{
                throw new EntityNotFoundException("Role does not exist");
            }
        }
        person.setRoles(roles);
        Person createdPerson = personRepository.save(person);
        return mapToDto(createdPerson);
    }
}

初始测试代码(存在问题)

package com.personregistrationspring;
import com.personregistrationspring.model.Role;
import com.personregistrationspring.model.RoleEnum;
import com.personregistrationspring.repository.PersonRepository;
import com.personregistrationspring.repository.RoleRepository;
import com.personregistrationspring.model.Person;
import com.personregistrationspring.model.dto.PersonDto;
import com.personregistrationspring.service.PersonServiceImpl;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.test.context.TestPropertySource;

import java.util.*;

import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;

@SpringBootTest
@ComponentScan(basePackages = "com.personregistrationspring.repository")
@TestPropertySource(locations = "classpath:application.properties")
public class PersonServiceImplTests {

    @Mock
    private PersonRepository personRepository;

    @Mock
    private RoleRepository roleRepository;

    @InjectMocks
    private PersonServiceImpl personService;

    @BeforeEach
    public void setUp(){
        MockitoAnnotations.openMocks(this);
    }

    @Test
    public void createPerson_shouldCreatePerson(){
        PersonDto personDto = new PersonDto();
        Role role = new Role(1, RoleEnum.ADMIN, null);
        personDto.setRoleIds(Set.of(1));
        List<Role> mockRoles = Arrays.asList(
                new Role(1, RoleEnum.ADMIN, null),
                new Role(2, RoleEnum.DEV, null),
                new Role(3, RoleEnum.QA, null)
        );
        when(roleRepository.findAll()).thenReturn(mockRoles);
        List<Role> roles = roleRepository.findAll();
        
        System.out.println(role.getRoleName());
        System.out.println(personDto.getRoleIds());
        when(roleRepository.findById(eq(1))).thenReturn(Optional.of(role));
        PersonDto createdPersonDto = personService.createPerson(personDto);

        assertNotNull(createdPersonDto);
    }
}

更新后的测试代码

import com.personregistrationspring.model.Role;
import com.personregistrationspring.model.RoleEnum;
import com.personregistrationspring.repository.PersonRepository;
import com.personregistrationspring.repository.RoleRepository;
import com.personregistrationspring.model.dto.PersonDto;
import com.personregistrationspring.service.PersonServiceImpl;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.junit.jupiter.api.extension.ExtendWith;

import java.util.Optional;
import java.util.Set;

import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.Mockito.when;

@ExtendWith(SpringExtension.class)
public class PersonServiceImplTests {

    @Mock
    private PersonRepository personRepository;

    @Mock
    private RoleRepository roleRepository;

    @InjectMocks
    private PersonServiceImpl personService;

    @BeforeEach
    public void setUp(){
        MockitoAnnotations.openMocks(this);
    }

    @Test
    public void createPerson_shouldCreatePerson(){
        PersonDto personDto = new PersonDto();
        Role role = new Role(1, RoleEnum.ADMIN, null);
        personDto.setRoleIds(Set.of(1));

        when(roleRepository.findById(1)).thenReturn(Optional.of(role));

        PersonDto createdPersonDto = personService.createPerson(personDto);

        assertNotNull(createdPersonDto);
    }
}

原因分析

  1. 初始测试类的混合模式冲突:
    初始测试同时使用了@SpringBootTest(启动Spring上下文的集成测试)和@Mock/@InjectMocks(Mockito单元测试),导致Spring上下文和Mockito的Mock实例相互干扰,Mock的RoleRepository没有被正确注入到PersonServiceImpl中,或者Service实际使用的是Spring上下文里的真实Repository而非Mock实例。

  2. 更新后的测试类缺失关键Mock:
    更新后的测试只Mock了roleRepository.findById,但createPerson方法中还调用了personRepository.save(person),如果没有Mock这个方法,它会返回null,导致后续mapToDto(null)可能抛出空指针异常或返回无效对象,同时也会影响测试的完整性。

  3. Autowired注入真实Repository的问题:
    如果想用真实Repository做集成测试,需要确保测试数据库中存在ID为1的Role数据,否则findById自然返回空。同时集成测试时不能使用@Mock,要通过@Autowired注入真实的PersonService和RoleRepository,避免实例冲突。

修正后的测试代码

方案1:纯Mockito单元测试(推荐,专注业务逻辑)

import com.personregistrationspring.model.Person;
import com.personregistrationspring.model.Role;
import com.personregistrationspring.model.RoleEnum;
import com.personregistrationspring.repository.PersonRepository;
import com.personregistrationspring.repository.RoleRepository;
import com.personregistrationspring.model.dto.PersonDto;
import com.personregistrationspring.service.PersonServiceImpl;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.util.Optional;
import java.util.Set;

import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;

public class PersonServiceImplTests {

    @Mock
    private PersonRepository personRepository;

    @Mock
    private RoleRepository roleRepository;

    @InjectMocks
    private PersonServiceImpl personService;

    @BeforeEach
    public void setUp(){
        MockitoAnnotations.openMocks(this);
    }

    @Test
    public void createPerson_shouldCreatePerson(){
        // 1. 构造测试数据
        PersonDto personDto = new PersonDto();
        // 注意:如果mapToEntity需要其他字段,这里要补充设置,比如姓名、地址等
        personDto.setRoleIds(Set.of(1));

        Role mockRole = new Role(1, RoleEnum.ADMIN, null);
        Person mockSavedPerson = new Person();
        // 给mockSavedPerson设置必要字段,确保mapToDto能正常转换
        mockSavedPerson.setRoles(Set.of(mockRole));

        // 2. Mock依赖方法
        when(roleRepository.findById(1)).thenReturn(Optional.of(mockRole));
        when(personRepository.save(any(Person.class))).thenReturn(mockSavedPerson);

        // 3. 执行测试
        PersonDto createdPersonDto = personService.createPerson(personDto);

        // 4. 断言
        assertNotNull(createdPersonDto);
        // 可以添加更多断言,比如验证角色是否正确设置
    }
}

方案2:Spring Boot集成测试(使用真实数据库)

import com.personregistrationspring.model.Role;
import com.personregistrationspring.model.RoleEnum;
import com.personregistrationspring.model.dto.PersonDto;
import com.personregistrationspring.service.PersonService;
import com.personregistrationspring.repository.RoleRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;

import java.util.Set;

import static org.junit.jupiter.api.Assertions.assertNotNull;

@SpringBootTest
@Transactional // 测试后自动回滚数据
public class PersonServiceImplIntegrationTests {

    @Autowired
    private PersonService personService;

    @Autowired
    private RoleRepository roleRepository;

    @BeforeEach
    public void setUp(){
        // 测试前插入必要的角色数据
        Role adminRole = new Role(1, RoleEnum.ADMIN, null);
        roleRepository.save(adminRole);
    }

    @Test
    public void createPerson_shouldCreatePerson(){
        PersonDto personDto = new PersonDto();
        // 设置PersonDto的所有必填字段
        personDto.setRoleIds(Set.of(1));

        PersonDto createdPersonDto = personService.createPerson(personDto);

        assertNotNull(createdPersonDto);
    }
}

内容的提问来源于stack exchange,提问作者Saida

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 04:09:52