gpt4 book ai didi

java - Spring Boot JPA 元模型不能为空!当尝试运行 JUnit/集成测试时

转载 作者:行者123 更新时间:2023-12-02 08:53:53 32 4
gpt4 key购买 nike

我在基于 Maven 的项目中使用 Spring Boot、JUnit 4 和 Mockito 来测试我的 Spring Boot 微服务 REST API。

因此,在启动时,DataInserter 类会从owner.json 和 cars.json 加载数据。

通常情况下,通过我的 REST 调用,一切正常,但我在设置单元测试和集成测试方面似乎出了问题。

项目结构:

myapi

├── pom.xml

├── src
   ├── main
│ │
  │   ├── java
│ │ │
  │   │   └── com
│ │ │
  │   │   └── myapi
│ │ │
   │   │   ├── MyApplication.java
│ │ │
   │   │   ├── bootstrap
│ │ │ │
   │   │   │   └── DataInserter.java
│ │ │
│ │ ├── controllers
│ │ │ │
   │   │   │   ├── OwnerController.java
│ │ │ │
   │   │   │   └── CarController.java
│ │ │
   │   │   ├── exceptions
│ │ │ │
   │   │   │   └── OwnerNotFoundException.java
│ │ │
   │   │   ├── model
│ │ │ │
   │   │   │   ├── AuditModel.java
│ │ │ │
  │   │   │   ├── Car.java
│ │ │ │
    │   │   │   └── Owner.java
│ │ │
  │   │   ├── repository
│ │ │ │
  │   │   │   ├── OwnerRepository.java
│ │ │ │
  │   │   │   └── CarRepository.java
│ │ │
   │   │   └── service
│ │ │
   │   │   ├── OwnerService.java
│ │ │
   │   │   ├── OwnerServiceImpl.java
│ │ │
   │   │   ├── CarService.java
│ │ │
   │   │   └── CarServiceImpl.java
   │   └── resources
│ │
   │   ├── application.properties
│ │
   │   ├── data
│ │ │
   │   │   ├── cars.json
│ │ │
   │   │   └── owners.json
│ │
   │   └── logback.xml
   └── test

   ├── java
│ │
   │   └── com
│ │
   │   └── myapi
│ │
   │   ├── MyApplicationTests.java
│ │
│ └── service
│ │ │
│ │ │
│ │ └── OwnerControllerTest.java
│ │
│ │
   │   └── controllers
│ │
│ │
   │   └── OwnerControllerIntegrationTest.java
   └── resources

   ├── application.properties

   ├── data
│ │
   │   ├── cars.json
│ │
   │   └── owners.json

   └── logback.xml
<小时/>

pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.myapi</groupId>
<artifactId>car-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>car-api</name>
<description>Car REST API</description>

<properties>
<java.version>1.8</java.version>
</properties>

<dependencies>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>
<小时/>
@Component
public class DataInserter implements ApplicationListener<ContextRefreshedEvent> {


@Value("classpath:data/owners.json")
Resource ownersResource;

@Value("classpath:data/cars.json")
Resource carsResource;

@Autowired
private OwnerService ownerService;

@Autowired
private CarsService carService;

@Override
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
List<Owner> populatedOwners = new ArrayList<>();
try {
Owner aOwner;

File ownersFile = ownersResource.getFile();
File carsFile = carsResource.getFile();

String ownersString = new String(Files.readAllBytes(ownersFile.toPath()));
String carsString = new String(Files.readAllBytes(carsFile.toPath()));

ObjectMapper mapper = new ObjectMapper();
List<Owner> owners = Arrays.asList(mapper.readValue(ownersString, Owner[].class));
List<ElectricCars> cars = Arrays.asList(mapper.readValue(carsString, ElectricCars[].class));

// Populate owners one by one
for (Owner owner : owners) {
aOwner = new Owner(owner.getName(), owner.getAddress(), owner.getCity(), owner.getState(), owner.getZipCode());
ownerService.createOwner(aOwner);
populatedOwners.add(aOwner);
}

// Populate owner cars one by one
for (int i = 0; i < populatedOwners.size(); i++) {
carService.createCars(populatedOwners.get(i).getId(), cars.get(i));
}

}
catch(IOException ioe) {
ioe.printStackTrace();;
}
}
}
<小时/>

src/main/resources/data/cars.json:

[
{
"make": "Honda",
"model": "Accord",
"year": "2020"
},
{
"make": "Nissan",
"model": "Maxima",
"year": "2019"
},
{
"make": "Toyota",
"model": "Prius",
"year": "2015"
},
{
"make": "Porsche",
"model": "911",
"year": "2017"
},
{
"make": "Hyundai",
"model": "Elantra",
"year": "2018"
},
{
"make": "Volkswagen",
"model": "Beatle",
"year": "1973"
},
{
"make": "Ford",
"model": "F-150",
"year": "2010"
},
{
"make": "Chevrolet",
"model": "Silverado",
"year": "2020"
},
{
"make": "Toyota",
"model": "Camary",
"year": "2018"
},
{
"make": "Alfa",
"model": "Romeo",
"year": "2017"
}
]
<小时/>

src/main/resources/data/owners.json:

[
{
"name": "Tom Brady"
"address": "123 Amherst Place",
"city": "Boston",
"state": "MA",
"zipCode": 53211
},
{
"name": "Kobe Bryant"
},
{
"name": "Mike Tyson"
},
{
"name": "Scottie Pippen"
},
{
"name": "John Madden"
},
{
"name": "Arnold Palmer"
},
{
"name": "Tiger Woods"
},
{
"name": "Magic Johnson"
},
{
"name": "George Foreman"
},
{
"name": "Charles Barkley"
}

]
<小时/>

src/main/resources/applications.properties:

server.servlet.context-path=/car-api
server.port=8080
server.error.whitelabel.enabled=false

# Database specific
spring.jpa.hibernate.ddl-auto=create
spring.datasource.url=jdbc:mysql://localhost:3306/car_db?useSSL=false
spring.datasource.ownername=root
spring.datasource.password=
<小时/>

审计模型:

@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
@JsonIgnoreProperties(
value = {"createdAt", "updatedAt"},
allowGetters = true
)
public abstract class AuditModel implements Serializable {

@ApiModelProperty(hidden = true)
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "created_at", nullable = false, updatable = false)
@CreatedDate
private Date createdAt;

@ApiModelProperty(hidden = true)
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "updated_at", nullable = false)
@LastModifiedDate
private Date updatedAt;

public Date getCreatedAt() {
return createdAt;
}

public void setCreatedAt(Date createdAt) {
this.createdAt = createdAt;
}

public Date getUpdatedAt() {
return updatedAt;
}

public void setUpdatedAt(Date updatedAt) {
this.updatedAt = updatedAt;
}
}
<小时/>

MyApplication.java

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@EnableJpaAuditing
@SpringBootApplication
public class MyApplication {

public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}

}
<小时/>

所有者实体:

@Entity
@Table(name = "owner")
public class Owner extends AuditModel {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@NotNull
private String name;


private String address,
private String city;
private String state;
private int zipCode;


@OneToMany(cascade = CascadeType.ALL,
fetch = FetchType.EAGER,
mappedBy = "owner")
private List<Car> cars = new ArrayList<>();

public Owner() {
}

// Getter & Setters omitted for brevity.
}

汽车实体:

@Entity
@Table(name="car")
public class Car extends AuditModel {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

String make;
String model;
String year;

@JsonIgnore
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "owner_id", nullable = false)
private Owner owner;

// Getter & Setters omitted for brevity.
}
<小时/>

所有者存储库:

@Repository
public interface OwnerRepository extends JpaRepository<Owner, Long> {
@Query(value = "SELECT * FROM owner WHERE name = ?", nativeQuery = true)
Owner findOwnerByName(String name);
}
<小时/>

汽车存储库:

@Repository
public interface CarRepository extends JpaRepository<Car, Long> {
}
<小时/>

所有者服务:

public interface OwnerService {

boolean createOwner(Owner owner);

Owner getOwnerByOwnerId(Long ownerId);

List<Owner> getAllOwners();

}
<小时/>

OwnerServiceImpl:

@Service
public class OwnerServiceImpl implements OwnerService {


@Autowired
OwnerRepository ownerRepository;

@Autowired
CarRepository carRepository;

@Override
public List<Owner> getAllOwners() {
return ownerRepository.findAll();
}

@Override
public boolean createOwner(Owner owner) {
boolean created = false;
if (owner != null) {
ownerRepository.save(owner);
created = true;
}
return created;
}

@Override
public Owner getOwnerByOwnerId(Long ownerId) {
Optional<Owner> owner = null;
if (ownerRepository.existsById(ownerId)) {
owner = ownerRepository.findById(ownerId);
}
return owner.get();
}
}
<小时/>

汽车服务:

public interface CarService {

boolean createCar(Long ownerId, Car car);
}
<小时/>

CarServiceImpl:

@Service
public class CarServiceImpl implements CarService {

@Autowired
OwnerRepository ownerRepository;

@Autowired
CarRepository carRepository;

@Override
public boolean createCar(Long ownerId, Car car) {
boolean created = false;
if (ownerRepository.existsById(ownerId)) {
Optional<Owner> owner = ownerRepository.findById(ownerId);
if (owner != null) {
List<Car> cars = owner.get().getCars();
cars.add(car);
owner.get().setCars(cars);
car.setOwner(owner.get());
carRepository.save(car);
created = true;
}
}
return created;
}

}

<小时/>

所有者 Controller :

@RestController
public class OwnerController {


private HttpHeaders headers = null;

@Autowired
OwnerService ownerService;

public OwnerController() {
headers = new HttpHeaders();
headers.add("Content-Type", "application/json");
}

@RequestMapping(value = { "/owners" }, method = RequestMethod.POST, produces = "APPLICATION/JSON")
public ResponseEntity<Object> createOwner(@Valid @RequestBody Owner owner) {
boolean isCreated = ownerService.createOwner(owner);
if (isCreated) {
return new ResponseEntity<Object>(headers, HttpStatus.OK);
}
else {
return new ResponseEntity<Object>(HttpStatus.NOT_FOUND);
}
}


@RequestMapping(value = { "/owners" }, method = RequestMethod.GET, produces = "APPLICATION/JSON")
public ResponseEntity<Object> getAllOwners() {
List<Owner> owners = ownerService.getAllOwners();

if (owners.isEmpty()) {
return new ResponseEntity<Object>(HttpStatus.NOT_FOUND);
}
return new ResponseEntity<Object>(owners, headers, HttpStatus.OK);
}


@RequestMapping(value = { "/owners/{ownerId}" }, method = RequestMethod.GET, produces = "APPLICATION/JSON")
public ResponseEntity<Object> getOwnerByOwnerId(@PathVariable Long ownerId) {
if (null == ownerId || "".equals(ownerId)) {
return new ResponseEntity<Object>(HttpStatus.NOT_FOUND);
}
Owner owner = ownerService.getOwnerByOwnerId(ownerId);
return new ResponseEntity<Object>(owner, headers, HttpStatus.OK);
}

}
<小时/>

汽车 Controller :

@RestController
public class CarController {

private HttpHeaders headers = null;

@Autowired
CarService carService;

public CarController() {
headers = new HttpHeaders();
headers.add("Content-Type", "application/json");
}

@RequestMapping(value = { "/cars/{ownerId}" }, method = RequestMethod.POST, produces = "APPLICATION/JSON")
public ResponseEntity<Object> createCarBasedOnOwnerId(@Valid @RequestBody Car car, Long ownerId) {
boolean isCreated = carService.createCar(ownerId, car);
if (isCreated) {
return new ResponseEntity<Object>(headers, HttpStatus.OK);
}
else {
return new ResponseEntity<Object>(HttpStatus.NOT_FOUND);
}
}

<小时/>

我的应用程序测试:

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class MyApplicationTests {

@Test
void contextLoads() {
}

}
<小时/>

所有者 Controller 测试:

@RunWith(SpringRunner.class)
@WebMvcTest(OwnerControllerTest.class)
@TestPropertySource(locations="classpath:application.properties")
public class OwnerControllerTest {

@Autowired
MockMvc mockMvc;

@MockBean
private OwnerService ownerService;

@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}

@Test
public void givenEndPointNotFoundThenReturn404() throws Exception {
Owner owner = new Owner("Tom Brady", "123 Amherst Place", "Boston", "MA", 53211);
Mockito.when(ownerService.getOwnerByOwnerId(1L)).thenReturn(null);

ResultActions resultActions = mockMvc.perform(
MockMvcRequestBuilders.get("/car-api/owners/0"));

resultActions.andExpect(status().is4xxClientError());
}
}
<小时/>

当我运行mvn clean install时,出现以下错误(位于target/sure-fire-reports/com.myapi.service.OwnerControllerTest.txt内) :

-------------------------------------------------------------------------------
Test set: com.myapi.service.OwnerControllerTest
-------------------------------------------------------------------------------
Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.32 s <<< FAILURE! - in com.myapi.service.OwnerControllerTest
givenEndPointNotFoundThenReturn404 Time elapsed: 0 s <<< ERROR!
java.lang.IllegalStateException: Failed to load ApplicationContext
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jpaAuditingHandler': Cannot resolve reference to bean 'jpaMappingContext' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jpaMappingContext': Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: JPA metamodel must not be empty!
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jpaMappingContext': Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: JPA metamodel must not be empty!
Caused by: java.lang.IllegalArgumentException: JPA metamodel must not be empty!

如果没有这个测试用例,DataInserter 会填充数据库,我就能够执行所有 REST 调用并获取所有适当的 JSON 有效负载。

最佳答案

您需要将@EnableJpaAuditing注释移动到单独的@Configuration类,否则即使对于不相关的应用程序切片也会加载它。

@Configuration
@EnableJpaAuditing
public class JpaAuditingConfiguration {}
<小时/>

@SpringBootApplication 类用作所有带有切片的测试的默认配置,因此任何附加到它的配置都会影响比您预期更多的测试。

文档中对此也有很好的解释:User Configuration and Slicing

关于java - Spring Boot JPA 元模型不能为空!当尝试运行 JUnit/集成测试时,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60606861/

32 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com