SpringでREST API(4.1.1。)を実装しています。特定のHTTPリクエストでは、応答として本文のないヘッドを返します。ただし、_ResponseEntity<Void>
_は機能していないようです。MockMvc
テストで呼び出された場合、406(受け入れられません)が返されます。パラメータ値(new ResponseEntity<String>( HttpStatus.NOT_FOUND )
)なしで_ResponseEntity<String>
_を使用すると正常に機能します。
方法:
_@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 );
}
}
_
テストケース(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() );
}
}
_
このスタックトレースで失敗します。
_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)
_
本体なしでResponseEntity
を返す場合、SpringはResponseEntity
戻り値の宣言で提供される型引数を使用して、本体の型を決定します。
だから
public ResponseEntity<Void> taxonomyPackageExists( @PathVariable final String key ) {
そのタイプはVoid
になります。その後、Springは登録されているすべてのHttpMessageConverter
インスタンスをループし、Void
型の本体を記述できるインスタンスを見つけます。そのようなHttpMessageConverter
は存在しないため(デフォルト構成の場合)、受け入れ可能な応答を生成できないと判断し、406 Not Acceptable HTTP応答を返します。
ResponseEntity<String>
を使用すると、SpringはString
を応答本文として使用し、StringHttpMessageConverter
をハンドラーとして見つけます。また、StringHttpMessageHandler
はあらゆるAccepted
メディアタイプのコンテンツを生成できるため、クライアントが要求しているapplication/xml
を処理できます。
iddy85のソリューション (現在は間違っていますが、ResponseEntity<?>
を示唆しているようです)で、ボディのタイプはObject
として推論されます。クラスパスに正しいライブラリがある場合、SpringはHttpMessageConverter
型のapplication/xml
を生成するために使用できるXML Object
にアクセスできます。
メソッドの実装はあいまいです。次を試して、コードを少し編集し、HttpStatus.NO_CONTENT
を使用しました。つまり、HttpStatus.OK
の代わりにコンテンツがありません。
サーバーは要求を実行しましたが、エンティティ本体を返す必要はなく、更新されたメタ情報を返したい場合があります。応答には、エンティティヘッダーの形式で新規または更新されたメタ情報が含まれている場合があります。これは、存在する場合、要求されたバリアントに関連付けられる必要があります。
[〜#〜] t [〜#〜]の任意の値は204では無視されますが、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 );
}
}
また、 docs を見るときに、少し簡潔であると思われるtypeパラメーターとSpringが指定したものを指定することもできません。
@RequestMapping(method = RequestMethod.HEAD, value = Constants.KEY )
public ResponseEntity taxonomyPackageExists( @PathVariable final String key ){
// ...
return new ResponseEntity(HttpStatus.NO_CONTENT);
}
Spring 4 MVC ResponseEntity.BodyBuilderおよびResponseEntity Enhancements Example のように記述できます。
_....
return ResponseEntity.ok().build();
....
return ResponseEntity.noContent().build();
_
更新:
返される値がOptional
である場合、便利なメソッドがあり、返されるok()
またはnotFound()
:
_return ResponseEntity.of(optional)
_
個人的には、空の応答を処理するために、統合テストでMockMvcResponseオブジェクトを次のように使用します。
MockMvcResponse response = RestAssuredMockMvc.given()
.webAppContextSetup(webApplicationContext)
.when()
.get("/v1/ticket");
assertThat(response.mockHttpServletResponse().getStatus()).isEqualTo(HttpStatus.NO_CONTENT.value());
私のコントローラーでは、次のような特定の場合に空の応答を返します。
return ResponseEntity.noContent().build();