Erro ao JUnit

Tentando testar esse método da classe: AddressResource, caso houver uma exception.

@RestController
@RequestMapping("api/addresses")
public class AddressResource {

	private final DatabaseAccessManager<Address> service;

	public AddressResource(DatabaseAccessManager<Address> service) { this.service = service; }

	@GetMapping("/get/{id}")
	public ResponseEntity<Address> findById(@PathVariable Long id){
		return ResponseEntity.status(HttpStatus.OK).body(service.findById(id));
	}

Mas aponta teste não sucedido e ainda o erro: java.lang.AssertionError: Status expected:<404> but was:<200>

package com.viegasb.webapi.resource;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import com.viegasb.webapi.entities.Address;
import com.viegasb.webapi.exceptions.GlobalExceptionHandler;
import com.viegasb.webapi.exceptions.services.EntityNotFoundException;
import com.viegasb.webapi.services.DatabaseAccessManager;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;

@ExtendWith(MockitoExtension.class)
public class AddressResourceTest {
	
	private Address address;
	private @Autowired MockMvc mockMvc;
	
	private @InjectMocks AddressResource resource;
	private @Mock DatabaseAccessManager<Address> database;
	
	@BeforeEach
	public void setup() {
		address = new Address(1L, "Los Angeles", "California", "South Park", "984726-94");
		mockMvc = MockMvcBuilders.standaloneSetup(resource)
					.build();
	}
	
	@Test
	public void shouldFindExistingId() throws Exception {
		when(database.findById(address.getId()))
			.thenReturn(address);
		
		mockMvc.perform(MockMvcRequestBuilders.get("/api/addresses/get/{id}", address.getId()))
			.andExpect(MockMvcResultMatchers.status().isOk())
			.andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON))
			.andExpect(MockMvcResultMatchers.jsonPath("$.id").value(address.getId()))
			.andReturn();
		
		verify(database).findById(address.getId());
		verifyNoMoreInteractions(database);
	}
	
	@Test
	public void shouldReturnNotFoundForNonExistingId() throws Exception {
		when(database.findById(address.getId()))
			.thenReturn(address);
		
		when(database.findById(2L))
			.thenThrow(new EntityNotFoundException(address.getId()));
		
		mockMvc.perform(MockMvcRequestBuilders.get("/api/addresses/get/{id}", address.getId()))
			.andExpect(MockMvcResultMatchers.status().isNotFound())
			.andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON))
			.andReturn();
		
		verify(database).findById(address.getId());
		verifyNoMoreInteractions(database);
	}
	
}

não sei qual o correto seria em informar a classe de exceção personalizada ou a classe que trata as exceptions do resource:

@ControllerAdvice
public class GlobalExceptionHandler {

	@ExceptionHandler(EntityNotFoundException.class)
	public ResponseEntity<StandardError> entityNotFound(EntityNotFoundException e, HttpServletRequest request) {
		var error = new StandardError(HttpStatus.NOT_FOUND.value(),e.getMessage(),request.getRequestURI());
		return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
	}

Tente alterar isso no seu teste para ver se funciona:

De

when(database.findById(2L))
  .thenThrow(new EntityNotFoundException(address.getId()));

Para

when(database.findById(address.getId()))
  .thenThrow(new EntityNotFoundException(address.getId()));
1 curtida

Acontece o seguinte erro:

jakarta.servlet.ServletException: Request processing failed: com.viegasb.webapi.exceptions.services.EntityNotFoundException: Entity {1}, Not found

Meio que o teste fica chamando a exception e não o resource para testar. Por isso que comentei se devo chamar a classe que realiza tratamento da exceção do resource ao inves da exception personalizada.

Lucas_Camara, talvez consegui resolver o problema, quebrei minha cabeça tentando encontrar e talvez consegui, pois a testabilidade foi 100%. Troquei o seguinte código:

when(database.findById(address.getId()))
			.thenReturn(address);
		
		when(database.findById(2L))
			.thenThrow(new EntityNotFoundException(address.getId()));
		
		mockMvc.perform(MockMvcRequestBuilders.get("/api/addresses/get/{id}", address.getId()))
			.andExpect(MockMvcResultMatchers.status().isNotFound())
			.andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON))
			.andReturn();

por esse:

@Test
	public void shouldReturnNotFoundForNonExistingId() throws Exception {					
		mockMvc.perform(MockMvcRequestBuilders.get("/api/adresses/get/{id}", 2L))
			.andExpect(MockMvcResultMatchers.status().isNotFound())
			.andReturn();

		verifyNoMoreInteractions(database);
	}

Não sei seria uma boa prática não utilizar when() ou verify(database).findById(address.getId()); Mas funcionou.

Explicarei sobre a confusão que realizer: Primeiro que estava adicionando a variável address, no mockMvc, ao inves de utilizar um number aleatorio:

mockMvc.perform(MockMvcRequestBuilders.get("/api/addresses/get/{id}", address.getId())) // aqui
			.andExpect(MockMvcResultMatchers.status().isNotFound())
			.andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON))
			.andReturn();

Ah vei, como amo ser desenvolvedor, adoro Java de coração. Estou desde há 6h, sentado resolvendo o problema e penso que consegui resolver :slight_smile:, Mas chego na hora e percebo que escrevi errado:

@Test
	public void shouldReturnNotFoundForNonExistingId() throws Exception {					
		mockMvc.perform(MockMvcRequestBuilders.get("/api/adresses/get/{id}", 2L)) // aqui ficou adresses
			.andExpect(MockMvcResultMatchers.status().isNotFound())
			.andReturn();

		verifyNoMoreInteractions(database);
	}

Por isso que ficou 100% de testabilidade, por que está errado. Ai fiquei mais uns minutos tentando entender e pesquisando, ai que penso algo de errado. Como vou configurar a classe GlobalExceptionHandler, se nem adicionei ao setup:

@BeforeEach
	public void setup() {
		address = new Address(1L, "Los Angeles", "California", "South Park", "984726-94");
		mockMvc = MockMvcBuilders
					.standaloneSetup(resource)
					.setControllerAdvice(new GlobalExceptionHandler())
					.build();
	}

Agora dei mais algumas alterações e consegui 100%:

@Test
	public void shouldReturnNotFoundForNonExistingId() throws Exception {
		when(database.findById(2L))
			.thenThrow(new EntityNotFoundException(2L));
		
		mockMvc.perform(MockMvcRequestBuilders.get("/api/addresses/get/{id}", 2L))
			.andExpect(MockMvcResultMatchers.status().isNotFound())
			.andReturn();
		
		verifyNoMoreInteractions(database);
	}

kkkkk

1 curtida