Встраиваемый сервер Micronaut против localhost - PullRequest
1 голос
/ 09 июля 2019

Я начинаю работать с micronaut и хотел бы понять разницу между тестированием контроллера с использованием локального хоста и использованием встроенного сервера

Например, у меня есть простой контроллер

@Controller("/hello")
public class HelloController {

  @Get("/test")
  @Produces(MediaType.TEXT_PLAIN)
  public String index() {
    return "Hello World";
  }  
}

и протестированный класс

@MicronautTest
public class HelloControllerTest {

  @Inject
  @Client("/hello")
  RxHttpClient helloClient;

  @Test
  public void testHello() {
    HttpRequest<String> request = HttpRequest.GET("/test");
    String body = helloClient.toBlocking().retrieve(request);

    assertNotNull(body);
    assertEquals("Hello World", body);
  }
}

Я получил журналы:

14:32:54.382 [nioEventLoopGroup-1-3] DEBUG mylogger - Sending HTTP Request: GET /hello/test
14:32:54.382 [nioEventLoopGroup-1-3] DEBUG mylogger - Chosen Server: localhost(51995)

Но тогда, в каких случаях нам нужен встроенный сервер?Зачем?где я могу найти документацию, чтобы понять это.Я читаю документацию от Micronaut, но мне не ясно, что на самом деле происходит и почему?как этот пример:

 @Test
  public void testIndex() throws Exception {

    EmbeddedServer server = ApplicationContext.run(EmbeddedServer.class);

    RxHttpClient client = server.getApplicationContext().createBean(RxHttpClient.class, server.getURL());

    assertEquals(HttpStatus.OK, client.toBlocking().exchange("/hello/status").status());
    server.stop();

}

1 Ответ

3 голосов
/ 09 июля 2019

В обоих случаях вы используете EmbeddedServer реализацию - NettyHttpServer. Это абстракция, представляющая реализацию сервера Micronaut (в данном случае NettyHttpServer).

Основное отличие заключается в том, что micronaut-test предоставляет компоненты и аннотации, которые значительно упрощают написание модульных тестов Micronaut HTTP. До micronaut-test вам приходилось запускать приложение вручную с помощью:

EmbeddedServer server = ApplicationContext.run(EmbeddedServer)

Затем вам нужно было подготовить HTTP-клиент, например:

HttpClient http = HttpClient.create(server.URL)

micronaut-test упрощает его добавление аннотации @MicronautTest к тестовому классу, и бегун запускает встроенный сервер и инициализирует все компоненты, которые вы можете добавить. Как и в случае с инъекцией RxHttpClient в вашем примере.

Второе, на что стоит обратить внимание, это то, что аннотация @MicronautTest также позволяет вам использовать аннотацию @MockBean для переопределения существующего компонента с помощью некоторого макета, который вы можете определить на уровне теста. По умолчанию @MicronautTest не проверяет какие-либо компоненты, поэтому приложение, которое запускается, отражает среду выполнения приложения 1: 1. То же самое происходит, когда вы запускаете EmbeddedServer вручную - это просто программный способ запуска обычного приложения Micronaut.

Таким образом, вывод довольно прост - если вы хотите писать меньше шаблонного кода в своих тестовых классах, используйте micronaut-test со всеми его аннотациями, чтобы сделать ваши тесты проще. Без этого вам придется вручную управлять всеми вещами (запускать приложение Micronaut, извлекать компоненты из контекста приложения вместо использования аннотации @Inject и т. Д.)

И последнее, но не менее важное, вот тот же тест, написанный без micronaut-test:

package com.github.wololock.micronaut.products

import io.micronaut.context.ApplicationContext
import io.micronaut.http.HttpRequest
import io.micronaut.http.HttpStatus
import io.micronaut.http.client.HttpClient
import io.micronaut.http.client.RxHttpClient
import io.micronaut.http.client.exceptions.HttpClientResponseException
import io.micronaut.runtime.server.EmbeddedServer
import spock.lang.AutoCleanup
import spock.lang.Shared
import spock.lang.Specification

class ProductControllerSpec extends Specification {

  @Shared
  @AutoCleanup
  EmbeddedServer server = ApplicationContext.run(EmbeddedServer)

  @Shared
  @AutoCleanup
  HttpClient http = server.applicationContext.createBean(RxHttpClient, server.URL)


  def "should return PROD-001"() {
    when:
    Product product = http.toBlocking().retrieve(HttpRequest.GET("/product/PROD-001"), Product)

    then:
    product.id == 'PROD-001'

    and:
    product.name == 'Micronaut in Action'

    and:
    product.price == 29.99
  }

  def "should support 404 response"() {
    when:
    http.toBlocking().exchange(HttpRequest.GET("/product/PROD-009"))

    then:
    def e = thrown HttpClientResponseException
    e.status == HttpStatus.NOT_FOUND
  }
}

В этом случае мы не можем использовать аннотацию @Inject, и единственный способ создания / внедрения bean-компонентов - это непосредственное использование объекта applicationContext. (Имейте в виду, что в этом случае компонент RxHttpClient не существует в контексте, и мы должны его создать - в случае micronaut-test этот компонент подготовлен для нас заранее.)

А вот тот же тест, который использует micronaut-test, чтобы сделать тест намного проще:

package com.github.wololock.micronaut.products

import io.micronaut.http.HttpRequest
import io.micronaut.http.HttpStatus
import io.micronaut.http.client.HttpClient
import io.micronaut.http.client.annotation.Client
import io.micronaut.http.client.exceptions.HttpClientResponseException
import io.micronaut.test.annotation.MicronautTest
import spock.lang.Specification

import javax.inject.Inject

@MicronautTest
class ProductControllerSpec extends Specification {

  @Inject
  @Client("/")
  HttpClient http

  def "should return PROD-001"() {
    when:
    Product product = http.toBlocking().retrieve(HttpRequest.GET("/product/PROD-001"), Product)

    then:
    product.id == 'PROD-001'

    and:
    product.name == 'Micronaut in Action'

    and:
    product.price == 29.99
  }

  def "should support 404 response"() {
    when:
    http.toBlocking().exchange(HttpRequest.GET("/product/PROD-009"))

    then:
    def e = thrown HttpClientResponseException
    e.status == HttpStatus.NOT_FOUND
  }
}

Меньше шаблонного кода и тот же эффект. Мы могли бы даже @Inject EmbeddedServer embeddedServer, если бы хотели получить к нему доступ, но в этом нет необходимости.

...