java - custom - responseentity vs responsebody
Primavera: devolver las respuestas HTTP vacĂas con ResponseEntity<Void> no funciona (5)
Estamos implementando una API REST con Spring (4.1.1.). Para ciertas solicitudes HTTP, nos gustaría devolver un encabezado sin cuerpo como respuesta. Sin embargo, el uso de ResponseEntity<Void>
no parece funcionar. Cuando se llama con una prueba de MockMvc
, se MockMvc
un 406 (No aceptable). El uso de ResponseEntity<String>
sin un valor de parámetro ( new ResponseEntity<String>( HttpStatus.NOT_FOUND )
) funciona bien.
Método:
@RequestMapping( method = RequestMethod.HEAD, value = Constants.KEY )
public ResponseEntity<Void> taxonomyPackageExists( @PathVariable final String key ) {
LOG.debug( "taxonomyPackageExists queried with key: {0}", key ); //$NON-NLS-1$
final TaxonomyKey taxonomyKey = TaxonomyKey.fromString( key );
LOG.debug( "Taxonomy key created: {0}", taxonomyKey ); //$NON-NLS-1$
if ( this.xbrlInstanceValidator.taxonomyPackageExists( taxonomyKey ) ) {
LOG.debug( "Taxonomy package with key: {0} exists.", taxonomyKey ); //$NON-NLS-1$
return new ResponseEntity<Void>( HttpStatus.OK );
} else {
LOG.debug( "Taxonomy package with key: {0} does NOT exist.", taxonomyKey ); //$NON-NLS-1$
return new ResponseEntity<Void>( HttpStatus.NOT_FOUND );
}
}
Caso de prueba (TestNG):
public class TaxonomyQueryControllerTest {
private XbrlInstanceValidator xbrlInstanceValidatorMock;
private TaxonomyQueryController underTest;
private MockMvc mockMvc;
@BeforeMethod
public void setUp() {
this.xbrlInstanceValidatorMock = createMock( XbrlInstanceValidator.class );
this.underTest = new TaxonomyQueryController( this.xbrlInstanceValidatorMock );
this.mockMvc = MockMvcBuilders.standaloneSetup( this.underTest ).build();
}
@Test
public void taxonomyPackageDoesNotExist() throws Exception {
// record
expect( this.xbrlInstanceValidatorMock.taxonomyPackageExists( anyObject( TaxonomyKey.class ) ) ).andStubReturn(
false );
// replay
replay( this.xbrlInstanceValidatorMock );
// do the test
final String taxonomyKey = RestDataFixture.taxonomyKeyString;
this.mockMvc.perform( head( "/taxonomypackages/{key}", taxonomyKey ).accept( //$NON-NLS-1$
MediaType.APPLICATION_XML ) ).andExpect( status().isNotFound() );
}
}
Falla con este seguimiento de pila:
FAILED: taxonomyPackageDoesNotExist
java.lang.AssertionError: Status expected:<404> but was:<406>
at org.springframework.test.util.AssertionErrors.fail(AssertionErrors.java:60)
at org.springframework.test.util.AssertionErrors.assertEquals(AssertionErrors.java:89)
at org.springframework.test.web.servlet.result.StatusResultMatchers$10.match(StatusResultMatchers.java:652)
at org.springframework.test.web.servlet.MockMvc$1.andExpect(MockMvc.java:153)
at de.zeb.control.application.xbrlstandalonevalidator.restservice.TaxonomyQueryControllerTest.taxonomyPackageDoesNotExist(TaxonomyQueryControllerTest.java:54)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:84)
at org.testng.internal.Invoker.invokeMethod(Invoker.java:714)
at org.testng.internal.Invoker.invokeTestMethod(Invoker.java:901)
at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:1231)
at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:127)
at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:111)
at org.testng.TestRunner.privateRun(TestRunner.java:767)
at org.testng.TestRunner.run(TestRunner.java:617)
at org.testng.SuiteRunner.runTest(SuiteRunner.java:334)
at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:329)
at org.testng.SuiteRunner.privateRun(SuiteRunner.java:291)
at org.testng.SuiteRunner.run(SuiteRunner.java:240)
at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52)
at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:86)
at org.testng.TestNG.runSuitesSequentially(TestNG.java:1224)
at org.testng.TestNG.runSuitesLocally(TestNG.java:1149)
at org.testng.TestNG.run(TestNG.java:1057)
at org.testng.remote.RemoteTestNG.run(RemoteTestNG.java:111)
at org.testng.remote.RemoteTestNG.initAndRun(RemoteTestNG.java:204)
at org.testng.remote.RemoteTestNG.main(RemoteTestNG.java:175)
Cuando devuelve una ResponseEntity
sin un cuerpo, Spring usa el argumento de tipo proporcionado en la declaración de tipo de ResponseEntity
para decidir sobre un tipo de cuerpo.
Entonces para
public ResponseEntity<Void> taxonomyPackageExists( @PathVariable final String key ) {
ese tipo será Void
. Spring luego pasará por todas sus instancias registradas de HttpMessageConverter
y encontrará una que pueda escribir un cuerpo para un tipo Void
. Dado que no existe tal HttpMessageConverter
(para una configuración predeterminada), decidirá que no puede producir una respuesta aceptable y, por lo tanto, devolverá una respuesta HTTP 406 No aceptable.
Con ResponseEntity<String>
, Spring usará String
como el cuerpo de respuesta y encontrará StringHttpMessageConverter
como manejador. Y dado que StringHttpMessageHandler
puede producir contenido para cualquier tipo de medio Accepted
, podrá manejar la application/xml
que su cliente está solicitando.
En la solución de iddy85 (que actualmente es incorrecta, pero parece sugerir ResponseEntity<?>
), El tipo para el cuerpo se inferirá como Object
. Si tiene las bibliotecas correctas en su ruta de clase, Spring tendrá acceso a un HttpMessageConverter
XML que puede usar para producir application/xml
para el tipo de Object
.
De acuerdo con las mejoras de Spring 4 MVC ResponseEntity.BodyBuilder y ResponseEntity, se podría escribir como:
....
return ResponseEntity.ok().build();
....
return ResponseEntity.noContent().build();
ACTUALIZAR:
Si el valor devuelto es Optional
hay un método conveniente, se devuelve ok()
o notFound()
:
return ResponseEntity.of(optional)
La implementación de su método es ambigua, intente lo siguiente, HttpStatus.NO_CONTENT
un poco su código y use HttpStatus.NO_CONTENT
es decir, 204 Sin contenido, en lugar de HttpStatus.OK
El servidor ha cumplido la solicitud pero no necesita devolver un cuerpo de entidad, y puede querer devolver información actualizada. La respuesta PUEDE incluir metainformación nueva o actualizada en forma de encabezados de entidad, que si están presentes DEBEN asociarse con la variante solicitada.
Cualquier valor de T se ignorará para 204, pero no para 404
public ResponseEntity<?> taxonomyPackageExists( @PathVariable final String key ) {
LOG.debug( "taxonomyPackageExists queried with key: {0}", key ); //$NON-NLS-1$
final TaxonomyKey taxonomyKey = TaxonomyKey.fromString( key );
LOG.debug( "Taxonomy key created: {0}", taxonomyKey ); //$NON-NLS-1$
if ( this.xbrlInstanceValidator.taxonomyPackageExists( taxonomyKey ) ) {
LOG.debug( "Taxonomy package with key: {0} exists.", taxonomyKey ); //$NON-NLS-1$
return new ResponseEntity<T>(HttpStatus.NO_CONTENT);
} else {
LOG.debug( "Taxonomy package with key: {0} does NOT exist.", taxonomyKey ); //$NON-NLS-1$
return new ResponseEntity<T>( HttpStatus.NOT_FOUND );
}
}
Personalmente, para lidiar con respuestas vacías, uso en mis Pruebas de Integración el objeto MockMvcResponse así:
MockMvcResponse response = RestAssuredMockMvc.given()
.webAppContextSetup(webApplicationContext)
.when()
.get("/v1/ticket");
assertThat(response.mockHttpServletResponse().getStatus()).isEqualTo(HttpStatus.NO_CONTENT.value());
y en mi controlador devuelvo una respuesta vacía en un caso específico como este:
return ResponseEntity.noContent().build();
Tampoco puede especificar el parámetro de tipo que parece un poco más limpio y qué pretendía Spring al mirar los docs :
@RequestMapping(method = RequestMethod.HEAD, value = Constants.KEY )
public ResponseEntity taxonomyPackageExists( @PathVariable final String key ){
// ...
return new ResponseEntity(HttpStatus.NO_CONTENT);
}