英文:
JUnit 5 test for Soap Web Service - ParameterResolutionException: No ParameterResolver registered
问题
以下是翻译好的内容:
正在尝试使用Java 1.8创建一个用于已发布的.NET Soap Web Service的JUnit 5测试。
目前,我正在按照代码库中的WebServiceClient中的模式来操作,该代码库包含一个```main()```方法。
---
WebServiceClient.java:
```java
import static com.google.common.base.Preconditions.checkNotNull;
public class WebServiceClient {
IPersonWebService service;
public WebServiceClient(IPersonWebService service) {
this.service = checkNotNull(service);
}
private PersonData getPersonData() throws Exception {
return service.getPersons(null);
}
public static void main(String [] args) {
IPersonWebService service = new IPersonWebServiceImpl().getBasicHttpBindingIPersonWebServiceImpl();
WebServiceClient client = new WebServiceClient(service);
PersonData personData = client.getPersonData();
System.out.println(personData.toString());
}
}
需要在以下位置实现相同类型的功能:
WebServiceTest.java:
import org.junit.Before;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import static com.google.common.base.Preconditions.checkNotNull;
public class WebServiceTest {
IPersonWebService service;
public WebServiceTest(IPersonWebService service) {
this.service = checkNotNull(service);
}
@Before
public void before(IPersonWebService service) {
this.service = checkNotNull(service);
}
@Test
public void testGetPersonData() throws Exception {
IPersonWebService service =
new IPersonWebServiceImpl().getBasicHttpBindingIPersonWebServiceImpl();
WebServiceTest client = new WebServiceTest(service);
PersonData personData = client.getPersonData();
assertThat(personData).isNotNull();
}
private PersonData getPersonData() throws Exception {
return service.getPersonData(null);
}
}
在IntelliJ IDEA中运行此代码会导致:
org.junit.jupiter.api.extension.ParameterResolutionException: 构造函数 [public com.myapp.WebServiceTest(com.myapp.IPersonWebService)] 中未注册参数 [com.myapp.IPersonWebService arg0] 的 ParameterResolver。
at java.util.Optional.orElseGet(Optional.java:267)
at java.util.ArrayList.forEach(ArrayList.java:1249)
at java.util.ArrayList.forEach(ArrayList.java:1249)
IPersonWebService.java:
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import javax.xml.bind.annotation.XmlSeeAlso;
import javax.xml.ws.RequestWrapper;
import javax.xml.ws.ResponseWrapper;
/**
* This class was generated by the JAX-WS RI.
* JAX-WS RI 2.2.9-b130926.1035
* Generated source version: 2.2
*
*/
@WebService(name = "IPersonWebService", targetNamespace = "http://sample.org/")
@XmlSeeAlso({
ObjectFactory.class
})
public interface IPersonWebService {
@WebMethod(operationName = "GetPersonData",
action = "http://sample.org/IPersonWebService/GetPersonData")
@WebResult(name = "GetVehiclesResult",
targetNamespace = "http://sample.org/")
@RequestWrapper(localName = "GetPersonData",
targetNamespace = "http://sample.org/",
className = "com.myapp.GetPersonData")
@ResponseWrapper(localName = "GetPersonDataResponse",
targetNamespace = "http://sample.org/",
className = "com.myapp.GetPersonDataResponse")
public {PersonData} getPersonData(
@WebParam(name = "applicationID",
targetNamespace = "http://sample.org/")
String applicationID)
throws IPersonWebServicetExceptionFaultMessage;
}
这包含了将被导入内存的实际WSDL。
IPersonWebServiceImpl.java:
import java.net.MalformedURLException;
import java.net.URL;
import javax.xml.namespace.QName;
import javax.xml.ws.Service;
import javax.xml.ws.WebEndpoint;
import javax.xml.ws.WebServiceClient;
import javax.xml.ws.WebServiceException;
import javax.xml.ws.WebServiceFeature;
/**
* This class was generated by the JAX-WS RI.
* JAX-WS RI 2.2.9-b130926.1035
* Generated source version: 2.2
*
*/
@WebServiceClient(name = "IPersonWebServiceImpl",
targetNamespace = "http://sample.org/",
wsdlLocation = "https://sample.com/WCF/IPersonWebServiceImpl.svc?singleWsdl")
public class IPersonWebServiceImpl
extends Service
{
private final static URL IPersonWebServiceImpl_WSDL_LOCATION;
private final static WebServiceException IPersonWebServiceImpl_EXCEPTION;
private final static QName IPersonWebServiceImpl_QNAME = new QName("http://sample.org/", "IPersonWebServiceImpl");
static {
URL url = null;
WebServiceException e = null;
try {
url = new URL("https://sample.com/WCF/IPersonWebServiceImpl.svc?singleWsdl");
} catch (MalformedURLException ex) {
e = new WebServiceException(ex);
}
IPersonWebServiceImpl_WSDL_LOCATION = url;
IPersonWebServiceImpl_EXCEPTION = e;
}
public IPersonWebServiceImpl() {
super(__getWsdlLocation(), IPersonWebServiceImpl_QNAME);
}
public IPersonWebServiceImpl(final WebServiceFeature... features) {
super(__getWsdlLocation(), IPersonWebServiceImpl_QNAME, features);
}
public IPersonWebServiceImpl(final URL wsdlLocation) {
super(wsdlLocation, IPersonWebServiceImpl_QNAME);
}
public IPersonWebServiceImpl(final URL wsdlLocation,
final WebServiceFeature... features) {
super(wsdlLocation, IPersonWebServiceImpl_QNAME, features);
}
public IPersonWebServiceImpl(final URL wsdlLocation,
final QName serviceName) {
super(wsdlLocation, serviceName);
}
public IPersonWebServiceImpl(final URL wsdlLocation,
final QName serviceName,
final WebServiceFeature... features) {
super(wsdlLocation, serviceName, features);
}
@WebEndpoint(name = "BasicHttpBinding_IPersonWebServiceImpl")
public IPersonWebServiceImpl getBasicHttpBindingIPersonWebServiceImpl() {
return super.getPort(new QName("http://sample.org/",
"BasicHttpBinding_IPersonWebServiceImpl"),
IPersonWebServiceImpl.class);
}
@WebEndpoint(name = "BasicHttpBinding_IPersonWebServiceImpl")
public IPersonWebServiceImpl getBasicHttpBindingIPersonWebServiceImpl(final WebServiceFeature... features) {
return super.getPort(new QName("http://sample.org/",
"BasicHttpBinding_IPersonWebServiceImpl"),
IPersonWebServiceImpl.class, features);
}
private static URL __getWsdlLocation() {
if (IPersonWebServiceImpl_EXCEPTION!= null) {
throw IPersonWebServiceImpl_EXCEPTION;
}
return IPersonWebServiceImpl_WSDL_LOCATION;
}
}
问题:
英文:
Am trying to create a JUnit 5 test for a published .NET Soap Web Service using Java 1.8.
Currently, I am following a pattern from a WebServiceClient in the codebase which contains a main()
method.
WebServiceClient.java:
import static com.google.common.base.Preconditions.checkNotNull;
public class WebServiceClient {
IPersonWebService service;
public WebServiceClient(IPersonWebService service) {
this.service = checkNotNull(service);
}
private PersonData getPersonData() throws Exception {
return service.getPersons(null);
}
public static void main(String [] args) {
IPersonWebService service = new IPersonWebServiceImpl().getBasicHttpBindingIPersonWebServiceImpl();
WebServiceClient client = new WebServiceClient(service);
PersonData personData = client.getPersonData();
System.out.println(personData.toString());
}
}
Need to following the same type of functionality in:
WebServiceTest.java:
import org.junit.Before;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import static com.google.common.base.Preconditions.checkNotNull;
public class WebServiceTest {
IPersonWebService service;
public WebServiceTest(IPersonWebService service) {
this.service = checkNotNull(service);
}
@Before
public void before(IPersonWebService service) {
this.service = checkNotNull(service);
}
@Test
public void testGetPersonData() throws Exception {
IPersonWebService service =
new IPersonWebServiceImpl().getBasicHttpBindingIPersonWebServiceImpl();
WebServiceTest client = new WebServiceTest(service);
PersonData personData = client.getPersonData();
assertThat(personData).isNotNull();
}
private PersonData getPersonData() throws Exception {
return service.getPersonData(null);
}
}
Running this within IntelliJ IDEA results in:
org.junit.jupiter.api.extension.ParameterResolutionException: No ParameterResolver registered for parameter [com.myapp.IPersonWebService arg0] in constructor [public com.myapp.WebServiceTest(com.myapp.IPersonWebService)].
at java.util.Optional.orElseGet(Optional.java:267)
at java.util.ArrayList.forEach(ArrayList.java:1249)
at java.util.ArrayList.forEach(ArrayList.java:1249)
IPersonWebService.java:
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import javax.xml.bind.annotation.XmlSeeAlso;
import javax.xml.ws.RequestWrapper;
import javax.xml.ws.ResponseWrapper;
/**
* This class was generated by the JAX-WS RI.
* JAX-WS RI 2.2.9-b130926.1035
* Generated source version: 2.2
*
*/
@WebService(name = "IPersonWebService", targetNamespace = "http://sample.org/")
@XmlSeeAlso({
ObjectFactory.class
})
public interface IPersonWebService {
@WebMethod(operationName = "GetPersonData",
action = "http://sample.org/IPersonWebService/GetPersonData")
@WebResult(name = "GetVehiclesResult",
targetNamespace = "http://sample.org/")
@RequestWrapper(localName = "GetPersonData",
targetNamespace = "http://sample.org/",
className = "com.myapp.GetPersonData")
@ResponseWrapper(localName = "GetPersonDataResponse",
targetNamespace = "http://sample.org/",
className = "com.myapp.GetPersonDataResponse")
public {PersonData} getPersonData(
@WebParam(name = "applicationID",
targetNamespace = "http://sample.org/")
String applicationID)
throws IPersonWebServicetExceptionFaultMessage;
}
This contains the actual WSDL that will be imported into memory.
IPersonWebServiceImpl.java:
import java.net.MalformedURLException;
import java.net.URL;
import javax.xml.namespace.QName;
import javax.xml.ws.Service;
import javax.xml.ws.WebEndpoint;
import javax.xml.ws.WebServiceClient;
import javax.xml.ws.WebServiceException;
import javax.xml.ws.WebServiceFeature;
/**
* This class was generated by the JAX-WS RI.
* JAX-WS RI 2.2.9-b130926.1035
* Generated source version: 2.2
*
*/
@WebServiceClient(name = "IPersonWebServiceImpl",
targetNamespace = "http://sample.org/",
wsdlLocation = "https://sample.com/WCF/IPersonWebServiceImpl.svc?singleWsdl")
public class IPersonWebServiceImpl
extends Service
{
private final static URL IPersonWebServiceImpl_WSDL_LOCATION;
private final static WebServiceException IPersonWebServiceImpl_EXCEPTION;
private final static QName IPersonWebServiceImpl_QNAME = new QName("http://sample.org/", "IPersonWebServiceImpl");
static {
URL url = null;
WebServiceException e = null;
try {
url = new URL("https://sample.com/WCF/IPersonWebServiceImpl.svc?singleWsdl");
} catch (MalformedURLException ex) {
e = new WebServiceException(ex);
}
IPersonWebServiceImpl_WSDL_LOCATION = url;
IPersonWebServiceImpl_EXCEPTION = e;
}
public IPersonWebServiceImpl() {
super(__getWsdlLocation(), IPersonWebServiceImpl_QNAME);
}
public IPersonWebServiceImpl(final WebServiceFeature... features) {
super(__getWsdlLocation(), IPersonWebServiceImpl_QNAME, features);
}
public IPersonWebServiceImpl(final URL wsdlLocation) {
super(wsdlLocation, IPersonWebServiceImpl_QNAME);
}
public IPersonWebServiceImpl(final URL wsdlLocation,
final WebServiceFeature... features) {
super(wsdlLocation, IPersonWebServiceImpl_QNAME, features);
}
public IPersonWebServiceImpl(final URL wsdlLocation,
final QName serviceName) {
super(wsdlLocation, serviceName);
}
public IPersonWebServiceImpl(final URL wsdlLocation,
final QName serviceName,
final WebServiceFeature... features) {
super(wsdlLocation, serviceName, features);
}
@WebEndpoint(name = "BasicHttpBinding_IPersonWebServiceImpl")
public IPersonWebServiceImpl getBasicHttpBindingIPersonWebServiceImpl() {
return super.getPort(new QName("http://sample.org/",
"BasicHttpBinding_IPersonWebServiceImpl"),
IPersonWebServiceImpl.class);
}
@WebEndpoint(name = "BasicHttpBinding_IPersonWebServiceImpl")
public IPersonWebServiceImpl getBasicHttpBindingIPersonWebServiceImpl(final WebServiceFeature... features) {
return super.getPort(new QName("http://sample.org/",
"BasicHttpBinding_IPersonWebServiceImpl"),
IPersonWebServiceImpl.class, features);
}
private static URL __getWsdlLocation() {
if (IPersonWebServiceImpl_EXCEPTION!= null) {
throw IPersonWebServiceImpl_EXCEPTION;
}
return IPersonWebServiceImpl_WSDL_LOCATION;
}
}
Question(s):
- When trying to follow along using the pattern inside
WebServiceClient.java
, why doesWebServiceTest.java
fail with this:
org.junit.jupiter.api.extension.ParameterResolutionException:
No ParameterResolver registered for parameter...
How to resolve this?
The WebServiceClient.java
works and shows PersonData which was obtained from the IPersonWebServiceImpl.java
(notice how this has the WSDL explicitly setup inside the static clause).
- Is there a 3rd party open source Java framework which I can use to import any type of WSDL (.NET or others) and test SOAP based endpoints using JUnit 5?
Come from a RESTful Web Services background and not a SOAP based background, so any suggestions would be most appreciated.
答案1
得分: 3
所以首先,感谢您提供大量的代码并清晰地表达问题。我看到您提问已经过去了几个月,但我会尽力帮助。
我认为存在两个问题:异常(简而言之,这是JUnit版本冲突)和为编写单元测试而构建代码结构。
ParameterResolutionException
@Test
是从JUnit5导入的,即'junit-jupiter',但是 @Before
来自JUnit4,即'junit-vintage' 或 'junit'。
import org.junit.Before; // junit4
import org.junit.jupiter.api.BeforeAll; // junit5
import org.junit.jupiter.api.Test; // junit5
因为 @Test
是来自 junit-jupiter,测试将使用JUnit5运行,与JUnit4不同,JUnit5允许在测试和构造方法中使用参数。 WebServiceTest
的构造函数需要一个参数。但您没有提供 IPersonWebService
的提供程序,因此JUnit5会中断 - 它无法解析它。
因此要修复这个问题:
- 移除构造函数
- 从
before
方法中删除参数 - 将
@Before
更改为@BeforeEach
(并修复导入)
我还建议(如果可能的话)排除您可能添加的任何JUnit4依赖项(junit-vintage
用于向后兼容性),或者可能从其他依赖项中偷偷添加的依赖项。这可以防止出现这样的混淆。
例如,使用Maven,可以查看和过滤依赖树以查看存在哪些JUnit版本以及它们的来源:
mvn dependency:tree -Dincludes="*junit*"
编写单元测试
我不确定应用程序的预期目标是什么,但是(正如我认为您已经发现的)很难进行测试。对我来说,它看起来确实很奇怪。WebServiceClient
有一个主方法(我猜它会被直接调用?),它立即创建API并调用它并返回结果 - 因此在“业务逻辑”和“API”之间没有分离。也许这个主方法是用来演示的?
最好将事物分开。希望这样可以使测试更加清晰。
让我们创建一个专门的 WebServiceClient
。这应该是唯一与 IPersonWebService
交互的类。
这里不应该有任何复杂的逻辑,只是带有基本异常处理的简单API调用。
// import generated soap stuff
public class WebServiceClient {
private final IPersonWebService service;
public WebServiceClient(IPersonWebService service) {
// 在构造函数中接收 API 的实例
this.service = service;
}
private PersonsData getPersonData() throws WebServiceClientException {
try {
// 调用 API
PersonsData persons = service.getPersonData(null);
if (personsData == null) {
throw new WebServiceClientException("Received null response from PersonsData :(");
}
return persons;
} catch (IPersonWebServicetExceptionFaultMessage e) {
// 将 SOAP 异常包装在我们自己的包装器中
// 最好不要让 SOAP 代码扩散到我们的项目中。
throw new WebServiceClientException("Tried fetching PersonsData, but got exception from API", e);
}
}
}
这是该服务的测试类。
每个 @Test
方法与其他方法完全独立,它拥有自己的小“气泡”,以保持测试的独立性。
我强烈推荐使用模拟框架,如Mockito,来创建虚拟实例。它非常强大。在这种情况下,它意味着我们可以轻松测试异常。Mockito 3 在JUnit5下工作得非常好 - 我在这里使用了参数注入来为测试创建模拟(Mockito在幕后提供了参数解析器)。
@ExtendWith(MockitoExtension.class)
class WebServiceClientTest {
@Test
public void testWebServiceClientCreated(
// 实际上不重要的是 IPersonWebService 的内部工作方式
// 使用 Mockito 来模拟它!
// 我们只关心它是否存在,那么就可以创建 WebServiceClient
@Mock
IPersonWebService service) {
WebServiceClient wsc = new WebServiceClient(service);
assertNotNull(wsc);
}
// 测试正常流程
@Test
public void testPersonsDataReturned(
// 同样,我们不关心 IPersonWebService 的内部工作方式,只要它返回数据即可
// 所以模拟它!
@Mock
IPersonWebService service,
// 也不重要的是 PersonsData 的内部工作方式,
// 只要返回一个对象即可
@Mock
PersonsData personsDataMock) {
// 设置模拟
when(service.getPersonsData(isNull())).thenReturn(personsDataMock);
WebServiceClient wsc = new WebServiceClient(service);
// 我们不需要在这里检查 WebServiceClient!我们已经有一个测试来做这个。
// assertNotNull(wsc);
PersonsData result = wsc.getPersonsData();
// 现在我们可以检查结果
assertNotNull(result);
assertEquals(personsDataMock, result);
}
// 再次,与测试正常流程相比,测试失败情况更有趣
// 在这种情况下,测试将失败!
// 我们最好修复 WebServiceClient,以处理来自 IPersonWebService 的意外异常
@Test
public void testWhenApiThrowsNullPointerExceptionExpectWebServiceClientException(
@Mock
IPersonWebService service) {
// 模拟抛出异常
NullPointerException npe = new NullPointerException("Another one of those weird external webservice exceptions");
doThrow(npe)
.when(service.getPersonsData(isNull()));
WebServiceClient wsc =
<details>
<summary>英文:</summary>
So firstly, thanks for providing loads of code and making your question clear. I see it's been a few months since you asked, but I'll try and help.
I think there's two problems: the exception (tldr it's a junit version clash), and structuring code for writing unit tests.
## ParameterResolutionException
`@Test` is imported from JUnit5, aka 'junit-jupiter', but `@Before` is from JUnit4, aka 'junit-vintage' or 'junit'.
```java
import org.junit.Before; // junit4
import org.junit.jupiter.api.BeforeAll; // junit5
import org.junit.jupiter.api.Test; // junit5
Because the @Test
is from junit-jupiter, the tests are run with JUnit5, which (unlike JUnit4) allows for parameters in test and constructors methods. WebServiceTest
's constructor needs a parameter. But you don't have a provider for IPersonWebService
, so JUnit5 breaks - it can't resolve it.
So to fix this:
- remove the constructor
- remove the parameter from the
before
method - change
@Before
to@BeforeEach
(and fix the imports)
I'd also recommend (if possible) that you exclude any JUnit4 dependencies that you might have added (junit-vintage
is for backwards compatibility), or might have been snuck in from other dependencies. It prevents confusions like this.
For example with maven, to see which JUnit versions there are, and their origins, view and filter the dependency tree:
mvn dependency:tree -Dincludes="*junit*"
Writing unit tests
I'm not sure what the application is intended to be, but (as I think you've found out) it's difficult to test. It certainly looks strange to me. WebServiceClient
has a main method (I guess it gets called directly?) that immediately creates the API and calls it and returns the result - so there's no separation between the 'business logic' and 'api'. Maybe that main method is there for demonstrating?
It would be best to split things up. Hopefully then it makes testing more clear.
Let's make a dedicated WebServiceClient
. This should be the only class that interacts with IPersonWebService
.
There shouldn't be any sort of fancy logic in here, just simple API calls with basic exception handling.
// import generated soap stuff
public class WebServiceClient {
private final IPersonWebService service;
public WebServiceClient(IPersonWebService service) {
// an instance of the API is received in the constructor
this.service = service;
}
private PersonsData getPersonData() throws WebServiceClientException {
try {
// call the API
PersonsData persons = service.getPersonData(null);
if (personsData == null) {
throw new WebServiceClientException("Received null response from PersonsData :(");
}
return persons;
} catch (IPersonWebServicetExceptionFaultMessage e) {
// wrap the SOAP exception in our own wrapper
// it's best not to let SOAP code spread into our project.
throw new WebServiceClientException("Tried fetching PersonsData, but got exception from API", e);
}
}
}
And here's the test class for that service.
Each @Test
method is completely independent from the others, it has its own little bubble to keep the tests independent.
I really recommend using a mocking framework, like Mockito, to create dummy instances. It's really powerful. In this case it means we can easily test exceptions. Mockito 3 works really well with JUnit5 - I've used the parameter injection here to make mocks for the tests (Mockito provides a Parameter Resolver under the hood).
@ExtendWith(MockitoExtension.class)
class WebServiceClientTest {
@Test
public void testWebServiceClientCreated(
// it's not actually important about the internal workings of IPersonWebService
// so use mockito to mock it!
// all we care about is that if it's there, then WebServiceClient can be created
@Mock
IPersonWebService service) {
WebServiceClient wsc = new WebServiceClient(service);
assertNotNull(wsc);
}
// test happy flow
@Test
public void testPersonsDataReturned(
// again, we don't care about the internals of IPersonWebService, just that it returns data
// so mock it!
@Mock
IPersonWebService service,
// it's also not important about the internals of PersonsData,
// just that it's an object that's returned
@Mock
PersonsData personsDataMock) {
// set up the mock
when(service.getPersonsData(isNull())).thenReturn(personsDataMock);
WebServiceClient wsc = new WebServiceClient(service);
// we don't need to check WebServiceClient here! We already have a test that does this.
// each test should only focus on one thing
// assertNotNull(wsc);
PersonsData result = wsc.getPersonsData();
// now we can check the result
assertNotNull(result);
assertEquals(personsDataMock, result);
}
// again, testing for failure is much more interesting than happy flows
// in this case, the test will fail!
// we'd better fix WebServiceClient to handle unexpected exceptions from IPersonWebService
@Test
public void testWhenApiThrowsNullPointerExceptionExpectWebServiceClientException(
@Mock
IPersonWebService service) {
// mock throwing an exception
NullPointerException npe = new NullPointerException("Another one of those weird external webservice exceptions");
doThrow()
.when(service.getPersonsData(isNull()));
WebServiceClient wsc = new WebServiceClient(service);
WebServiceClientException thrownException = assertThrows(WebServiceClientException.class,
() -> wsc.getPersonsData()
);
// now we can check the result
assertNotNull(thrownException);
assertEquals("Tried fetching PersonsData, but got exception from API", thrownException.getMessage());
assertEquals(npe, thrownException.getCause());
}
}
And a nice wrapper for any exceptions when dealing with the API.
class WebServiceClientException extends Exception {
public WebServiceClientException(String message) {
super(message);
}
public WebServiceClientException(String message, Exception cause) {
super(message, cause);
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论