#java #web-services #soap #junit #junit5
#java #веб-сервисы #soap #junit #junit5
Вопрос:
Я пытаюсь создать тест JUnit 5 для опубликованной веб-службы .NET Soap с использованием Java 1.8.
В настоящее время я следую шаблону из WebServiceClient в кодовой базе, который содержит main()
метод.
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());
}
}
Необходимо следовать тому же типу функциональности в:
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: 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.WebResu<
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;
}
}
Вопрос (ы):
- Почему при попытке следовать шаблону внутри
WebServiceClient.java
WebServiceTest.java
происходит сбой:
org.junit.jupiter.api.extension.ParameterResolutionException:
No ParameterResolver registered for parameter...
Как это решить?
WebServiceClient.java
Работает и показывает PersonData, которые были получены из IPersonWebServiceImpl.java
(обратите внимание, как это явно настроило WSDL внутри статического предложения).
- Существует ли сторонняя платформа Java с открытым исходным кодом, которую я могу использовать для импорта любого типа WSDL (.NET или других) и тестирования конечных точек на основе SOAP с использованием JUnit 5?
Исходят из фона веб-служб RESTful, а не из фона на основе SOAP, поэтому любые предложения будут высоко оценены.
Ответ №1:
Итак, во-первых, спасибо за предоставление большого количества кода и разъяснение вашего вопроса. Я вижу, что прошло несколько месяцев с тех пор, как вы спросили, но я постараюсь помочь.
Я думаю, что есть две проблемы: исключение (tldr это столкновение версий 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) допускает параметры в методах test и constructors . 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) {
// 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);
}
}
}
И вот тестовый класс для этой службы.
Каждый @Test
метод полностью независим от других, у него есть свой собственный маленький пузырь, чтобы тесты оставались независимыми.
Я действительно рекомендую использовать mocking framework, например Mockito, для создания фиктивных экземпляров. Это действительно мощно. В данном случае это означает, что мы можем легко тестировать исключения. Mockito 3 очень хорошо работает с JUnit5 — я использовал здесь ввод параметров, чтобы создавать макеты для тестов (Mockito предоставляет распознаватель параметров под капотом).
@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());
}
}
И хорошая оболочка для любых исключений при работе с API.
class WebServiceClientException extends Exception {
public WebServiceClientException(String message) {
super(message);
}
public WebServiceClientException(String message, Exception cause) {
super(message, cause);
}
}