I’m having an issue with my Spring Boot integration test using @MockMvc. When I run the test, the jsonPath assertions fail because there is nothing in the response body ($).
package env.starwars.web;
import static org.mockito.Mockito.when;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import com.fasterxml.jackson.databind.ObjectMapper;
import env.starwars.domain.Planet;
import env.starwars.domain.PlanetService;
import env.starwars.factory.PlanetFactory;
@WebMvcTest()
@AutoConfigureMockMvc
public class PlanetControllerTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@MockitoBean
private PlanetService planetService;
@Test
public void createPlanet_WithValidData_ReturnCreated() throws Exception {
Planet planet = PlanetFactory.create();
when(planetService.create(planet)).thenReturn(planet);
mockMvc.perform(MockMvcRequestBuilders.post("/planets")
.content(objectMapper.writeValueAsString(planet)).contentType(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.status().isCreated())
.andDo(MockMvcResultHandlers.print())
.andExpect(MockMvcResultMatchers.jsonPath("$.name").value(planet.getName()))
.andExpect(MockMvcResultMatchers.jsonPath("$.terrain").value(planet.getTerrain()))
.andExpect(MockMvcResultMatchers.jsonPath("$.climate").value(planet.getClimate()));
}
}
My PlanetFactory:
package env.starwars.factory;
import env.starwars.domain.Planet;
import env.starwars.web.dto.PlanetDto;
public class PlanetFactory {
public static Planet create() {
return new Planet("name", "climate", "terrain");
}
public static Planet createWithInvalidData() {
return new Planet("", "", "");
}
public static PlanetDto createDto() {
return new PlanetDto(1L, "name", "climate", "terrain");
}
public static PlanetDto createAalothDto() {
return new PlanetDto(1L, "Aaloth", "Unknown", "Terrestrial");
}
public static Planet createAaloth() {
return new Planet(1L, "Aaloth", "Unknown", "Terrestrial");
}
}
Here's a sample of the debug console output:
19:04:00.130 [main] INFO org.springframework.test.context.support.AnnotationConfigContextLoaderUtils -- Could not detect default configuration classes for test class [env.starwars.web.PlanetControllerTest]: PlanetControllerTest does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
19:04:00.247 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper -- Found @SpringBootConfiguration env.starwars.StarWarsApplication for test class env.starwars.web.PlanetControllerTest
19:04:00.300 [main] INFO org.springframework.boot.devtools.restart.RestartApplicationListener -- Restart disabled due to context in which it is running
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.4.1)
2025-01-12T19:04:00.557-03:00 INFO 177734 --- [ main] env.starwars.web.PlanetControllerTest : Starting PlanetControllerTest using Java 17.0.13 with PID 177734 (started by fynko in /home/fynko/codes/star-wars)
2025-01-12T19:04:00.558-03:00 INFO 177734 --- [ main] env.starwars.web.PlanetControllerTest : No active profile set, falling back to 1 default profile: "default"
OpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended
2025-01-12T19:04:02.060-03:00 INFO 177734 --- [ main] o.s.t.web.servlet.TestDispatcherServlet : Initializing Servlet ''
2025-01-12T19:04:02.063-03:00 INFO 177734 --- [ main] o.s.t.web.servlet.TestDispatcherServlet : Completed initialization in 1 ms
2025-01-12T19:04:02.089-03:00 INFO 177734 --- [ main] env.starwars.web.PlanetControllerTest : Started PlanetControllerTest in 1.792 seconds (process running for 2.431)
MockHttpServletRequest:
HTTP Method = POST
Request URI = /planets
Parameters = {}
Headers = [Content-Type:"application/json;charset=UTF-8", Content-Length:"65"]
Body = {"id":null,"name":"name","climate":"climate","terrain":"terrain"}
Session Attrs = {}
Handler:
Type = env.starwars.web.PlanetController
Method = env.starwars.web.PlanetController#create(Planet)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 201
Error message = null
Headers = []
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
MockHttpServletRequest:
HTTP Method = POST
Request URI = /planets
Parameters = {}
Headers = [Content-Type:"application/json;charset=UTF-8", Content-Length:"65"]
Body = {"id":null,"name":"name","climate":"climate","terrain":"terrain"}
Session Attrs = {}
Handler:
Type = env.starwars.web.PlanetController
Method = env.starwars.web.PlanetController#create(Planet)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 201
Error message = null
Headers = []
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
My Controller:
package env.starwars.web;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import env.starwars.domain.Planet;
import env.starwars.domain.PlanetService;
import env.starwars.web.dto.PlanetDto;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@RestController
@RequestMapping("/planets")
public class PlanetController {
@Autowired
private PlanetService planetService;
@PostMapping()
public ResponseEntity<Planet> create(@RequestBody Planet planet) {
Planet planetCreated = planetService.create(planet);
return ResponseEntity.status(HttpStatus.CREATED).body(planetCreated);
}
@GetMapping("/{id}")
public ResponseEntity<PlanetDto> getPlanetById(@PathVariable(name = "id") Long id) {
return planetService.getById(id).map(planet -> ResponseEntity.ok(planet))
.orElseGet(() -> ResponseEntity.notFound().build());
}
@GetMapping("/name/{name}")
public ResponseEntity<PlanetDto> getPlanetByName(@PathVariable(name = "name") String name) {
return planetService.getByName(name).map(planet -> ResponseEntity.ok(planet))
.orElseGet(() -> ResponseEntity.notFound().build());
}
@GetMapping
public ResponseEntity<List<PlanetDto>> listPlanets(@RequestParam(required = false) String terrain,
@RequestParam(required = false) String climate) {
List<PlanetDto> planets = planetService.listPlanets(terrain, climate);
return ResponseEntity.ok().body(planets);
}
@DeleteMapping("/{id}")
public ResponseEntity<PlanetDto> deletePlanetById(@PathVariable(name = "id") Long id) {
planetService.deleteById(id);
return ResponseEntity.noContent().build();
}
}
My service:
package env.starwars.domain;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import org.springframework.data.domain.Example;
import org.springframework.stereotype.Service;
import env.starwars.utils.MapperPlanet;
import env.starwars.web.dto.PlanetDto;
@Service
public class PlanetService {
private final PlanetRepository planetRepository;
private final MapperPlanet mapperPlanet;
public PlanetService(PlanetRepository planetRepository, MapperPlanet mapperPlanet) {
this.planetRepository = planetRepository;
this.mapperPlanet = mapperPlanet;
}
public Planet create(Planet planet) {
return planetRepository.save(planet);
}
public Optional<PlanetDto> getById(Long id) {
Optional<Planet> planet = planetRepository.findById(id);
if (planet.isPresent()) {
PlanetDto planetDto = mapperPlanet.toPlanetDto(planet.get());
return Optional.of(planetDto);
}
return Optional.empty();
}
public Optional<PlanetDto> getByName(String name) {
Optional<Planet> planet = planetRepository.findByName(name);
if (planet.isPresent()) {
PlanetDto planetDto = mapperPlanet.toPlanetDto(planet.get());
return Optional.of(planetDto);
}
return Optional.empty();
}
public List<PlanetDto> listPlanets(String terrain, String climate) {
Example<Planet> query = QueryBuilder.makeQuery(new Planet(climate, terrain));
return planetRepository.findAll(query).stream().map(mapperPlanet::toPlanetDto)
.collect(Collectors.toList());
}
public void deleteById(Long id) {
planetRepository.deleteById(id);
}
}
Planet:
package env.starwars.domain;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotEmpty;
@Entity
@Table(name = "planets")
public class Planet {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false)
@NotEmpty()
private String name;
@Column(nullable = false)
@NotEmpty()
private String climate;
@Column(nullable = false)
@NotEmpty()
private String terrain;
public Planet(String climate, String terrain) {
this.climate = climate;
this.terrain = terrain;
}
public Planet(String name, String climate, String terrain) {
this.name = name;
this.climate = climate;
this.terrain = terrain;
}
public Planet(Long id, String name, String climate, String terrain) {
this.id = id;
this.name = name;
this.climate = climate;
this.terrain = terrain;
}
public Planet() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getClimate() {
return climate;
}
public void setClimate(String climate) {
this.climate = climate;
}
public String getTerrain() {
return terrain;
}
public void setTerrain(String terrain) {
this.terrain = terrain;
}
@Override
public String toString() {
return "Planet [id=" + id + ", name=" + name + ", climate=" + climate + ", terrain=" + terrain + "]";
}
}
Anyone can help me? I would really appreciate any help in understanding why the response body is empty in my integration test. Thank you in advance!
You don't have an equals()
method on your Planet
class, so this stubbing:
when(planetService.create(planet)).thenReturn(planet);
doesn't match, and planetService.create()
is returning null.
Using your Planet
class I was able to replicate your issue, but by adding an equals()
method to Planet
, the test worked.