基于Jersey開發的一個操作OpenStack的REST服務,利用Jersey的Test Framework編寫單元測試類如下:
public class RestAddressTest extends JerseyTest {
Integer autoId = 1;
@BeforeClass
public void before() throws Exception {
super.setUp();
}
@AfterClass
public void after() throws Exception {
super.tearDown();
}
@Override
protected Application configure() {
return new ResourceConfig(RestAddress.class);
}
@Test(priority = 0)
public void testAdd() {
Address ac = new Address();
ac.setGateway("1.1.1.1");
ac.setName("CLOUD_TEST_BJ");
ac.setNicName("ipv4");
Response res = target("address").request(MediaType.APPLICATION_JSON).post(Entity.entity(ac, MediaType.APPLICATION_JSON), Response.class);
Assert.assertEquals(200, res.getStatus());
}
@Test(priority = 1)
public void testGet() {
Response res = target("address").request(MediaType.APPLICATION_JSON).get();
List<Address> acl = res.readEntity(new GenericType<List<Address>>() {});
this.autoId = acl.get(0).getAutoId();
Assert.assertEquals(1, acl.size());
}
@Test(priority = 2)
public void testDelete() {
Response response = target("address").path(String.valueOf(autoId)).request(MediaType.APPLICATION_JSON).delete();
Assert.assertEquals(200, response.getStatus());
}
}
但是在測試時,卻始終遇到如下異常:
...
java.lang.IllegalArgumentException: attempt to create delete event with null entity
at org.hibernate.event.spi.DeleteEvent.<init>(DeleteEvent.java:31)
at org.hibernate.internal.SessionImpl.delete(SessionImpl.java:911)
...
[03:39:54,116 ERROR] rollbackTxn - Transaction.rollback
[03:39:54,143 INFO ] Stopped [email protected]{HTTP/1.1}{0.0.0.0:9998}
[03:39:54,146 WARN ] /address/1
java.lang.RuntimeException: java.lang.IllegalArgumentException: Status code of the supplied response [500] is not from the required status code family "CLIENT_ERROR".
at org.glassfish.jersey.jetty.JettyHttpContainer.handle(JettyHttpContainer.java:197)
...
[03:39:54,147 WARN ] Could not send response error 500: java.lang.RuntimeException: java.lang.IllegalArgumentException: Status code of the supplied response [500] is not from the required status code family "CLIENT_ERROR".
Oct 24, 2018 5:39:54 AM org.glassfish.jersey.test.jetty.JettyTestContainerFactory$JettyTestContainer <init>
INFO: Creating JettyTestContainer configured at the base URI http://localhost:9998/
[03:39:54,275 INFO ] jetty-9.2.14.v20151106
[03:39:54,279 INFO ] Started [email protected]{HTTP/1.1}{0.0.0.0:9998}
[03:39:54,280 INFO ] Started @5991ms
[03:39:54,312 INFO ] Stopped [email protected]{HTTP/1.1}{0.0.0.0:9998}
...
Tests run: 4, Failures: 3, Errors: 0, Skipped: 0, Time elapsed: 4.734 sec <<< FAILURE!
Results :
Failed tests: testAdd(com.mycompany.myapp.rest.RestAddressTest): RESTEASY004655: Unable to invoke request
testGet(com.mycompany.myapp.rest.RestAddressTest): RESTEASY003145: Unable to find a MessageBodyReader of content-type application/json and type interface java.util.List
testDelete(com.mycompany.myapp.rest.RestAddressTest): expected [500] but found [200]
...
分析異常,可以發現在執行測試用例testDelete()時異常,這裡的資訊有很強的迷惑性。事實上,在測試用例testDelete()之前執行的testAdd()和testGet()都已經發生了異常,但是卻沒有輸出錯誤資訊。
繼續分析發現這應該是個“CLIENT_ERROR”。
在最後的測試結果中,終于暴露了錯誤的根源。原來,發生錯誤的是RESTEASY,等等,我們利用Jersey的測試架構進行單元測試,哪裡來的RestEasy呢?
檢查項目依賴,發現果然同時存在Jersey-Client和RestEasy-Client。突然想到,存在OpenStack的OpenStack4j就是依賴了RestEasy。檢視OpenStack4j 3.1.0果然是其引入了RestEasy。
那麼RestEasy為什麼會在這裡被調用呢?感謝StackOverflow,我很快找到了答案。
原來,同樣作為JAX-RS的實作,Jersey和RestEasy都擴充了javax.ws.rs.client.ClientBuilder類,并實作了javax.ws.rs.client.Client接口。
其中,Jersey提供的是org.glassfish.jersey.client.JerseyClientBuilder和org.glassfish.jersey.client.JerseyClient,
而RestEasy提供的是org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder和org.jboss.resteasy.client.jaxrs.ResteasyClient。
在同時存在兩個JAX-RS實作的時候,由于JAX-RS采用了Java SPI的服務實作注入機制。RestEasy響應了這種機制,聲明了ResteasyClientBuilder實作如下:
而Jersey對此機制卻無動于衷,實作中根本沒有給出JerseyClientBuilder的實作,如下所示:
是以,ResteasyClientBuilder的優先級高于JerseyClientBuilder而被采用。這樣,在測試過程中,用戶端事實上使用的是RestEasy的ResteasyClient,而非Jersey測試架構期望的JerseyClient。
找到問題的根源,解決起來也很容易了,隻要在測試類中重寫JerseyTest的getClient()方法,明确指定JerseyClient即可,修改測試類如下:
@Override
public Client getClient() {
return JerseyClientBuilder.createClient();
}
參考連結:
https://jersey.github.io/documentation/latest/test-framework.html
https://github.com/jersey/jersey/tree/master/test-framework
https://stackoverflow.com/questions/36348675/unable-to-test-jax-rs-with-json-entity
https://stackoverflow.com/questions/48337023/eclipse-jerseytest-getclient-returns-resteasyclient
https://github.com/ContainX/openstack4j/blob/3.1.0/connectors/resteasy/pom.xml
https://github.com/resteasy/Resteasy/blob/master/resteasy-client/src/main/resources/META-INF/services/javax.ws.rs.client.ClientBuilder
https://github.com/jersey/jersey/tree/master/core-client/src/main