Тест JUnit 5 для веб-службы Soap — Исключение ParameterResolutionException: не зарегистрирован решатель параметров

#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);
  }
}