Nous implémentons une API REST avec Spring (4.1.1.). Pour certaines requêtes HTTP, nous aimerions renvoyer une tête sans corps comme réponse. Cependant, en utilisant ResponseEntity<Void>
ne semble pas fonctionner. Lorsqu'il est appelé avec un test MockMvc
, un 406 (non acceptable) est renvoyé. Utilisation de ResponseEntity<String>
sans valeur de paramètre (new ResponseEntity<String>( HttpStatus.NOT_FOUND )
) fonctionne bien.
Méthode:
@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 );
}
}
Cas de test (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() );
}
}
Échoue avec cette trace de pile:
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)
Lorsque vous renvoyez un ResponseEntity
sans corps, Spring utilise l'argument type fourni dans la déclaration de type de retour ResponseEntity
pour décider d'un type de corps.
Donc pour
public ResponseEntity<Void> taxonomyPackageExists( @PathVariable final String key ) {
ce type sera Void
. Spring parcourra ensuite toutes ses instances HttpMessageConverter
enregistrées et en trouvera une qui peut écrire un corps pour un type Void
. Puisqu'il n'existe pas de HttpMessageConverter
(pour une configuration par défaut), il décidera qu'il ne peut pas produire une réponse acceptable et retournera donc une réponse HTTP 406 non acceptable.
Avec ResponseEntity<String>
, Spring utilisera String
comme corps de réponse et trouvera StringHttpMessageConverter
comme gestionnaire. Et puisque StringHttpMessageHandler
peut produire du contenu pour n'importe quel type de support Accepted
, il pourra gérer le application/xml
que votre client demande.
In solution iddy85 (ce qui est actuellement faux, mais semble suggérer ResponseEntity<?>
), le type du corps sera déduit comme Object
. Si vous avez les bibliothèques correctes dans votre chemin de classe, Spring aura accès à un XML HttpMessageConverter
qu'il pourra utiliser pour produire application/xml
pour le type Object
.
L'implémentation de votre méthode est ambiguë, essayez ce qui suit, modifiez un peu votre code et utilisez HttpStatus.NO_CONTENT
c'est-à-dire 204 Pas de contenu comme à la place de HttpStatus.OK
Le serveur a répondu à la demande mais n'a pas besoin de renvoyer un corps d'entité et peut vouloir renvoyer une métainformation mise à jour. La réponse PEUT inclure une métainformation nouvelle ou mise à jour sous la forme d'en-têtes d'entité qui, s'ils sont présents, DEVRAIENT être associés à la variante demandée.
Toute valeur de T sera ignorée pour 204, mais pas pour 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 );
}
}
Vous ne pouvez pas non plus spécifier le paramètre de type qui semble un peu plus propre et ce que Spring voulait en regardant docs :
@RequestMapping(method = RequestMethod.HEAD, value = Constants.KEY )
public ResponseEntity taxonomyPackageExists( @PathVariable final String key ){
// ...
return new ResponseEntity(HttpStatus.NO_CONTENT);
}
Selon Spring 4 MVC ResponseEntity.BodyBuilder et ResponseEntity Enhancements Example il pourrait être écrit comme suit:
....
return ResponseEntity.ok().build();
....
return ResponseEntity.noContent().build();
MISE À JOUR:
Si la valeur renvoyée est Optional
, il existe une méthode pratique, renvoyée ok()
ou notFound()
:
return ResponseEntity.of(optional)
Personnellement, pour gérer les réponses vides, j'utilise dans mes tests d'intégration l'objet MockMvcResponse comme ceci:
MockMvcResponse response = RestAssuredMockMvc.given()
.webAppContextSetup(webApplicationContext)
.when()
.get("/v1/ticket");
assertThat(response.mockHttpServletResponse().getStatus()).isEqualTo(HttpStatus.NO_CONTENT.value());
et dans mon contrôleur, je renvoie une réponse vide dans un cas spécifique comme celui-ci:
return ResponseEntity.noContent().build();