当前位置: 首页 > 知识库问答 >
问题:

Micronaut 3:如何使用PubSubEmulatorContainer

景星光
2023-03-14

更新:链接到repo被移动到回答,因为repo现在使用下面回答中的代码更新。

问题描述

当前代码正在运行,但它正在使用谷歌/云sdk的gcloud测试版模拟器pubsub进行集成测试。

  • 由于google/cloud d-sdk映像的大小,集成测试很慢
  • pubsub模拟器必须在固定端口上运行,似乎没有办法告诉Micronaut模拟器在哪个端口上运行

我需要在maven surefire插件中设置以下环境变量

<environmentVariables>
    <PUBSUB_EMULATOR_HOST>localhost:8085</PUBSUB_EMULATOR_HOST>
</environmentVariables>

如何在Spring Boot中完成此操作

根据测试容器|Gcloud模块,在Spring Boot中使用PubSubEmulatorContainer实现集成测试的正确方法如下:https://github.com/saturnism/testcontainers-gcloud-examples/blob/main/springboot/pubsub-example/src/test/java/com/example/springboot/pubsub/PubSubIntegrationTests.java

这将在随机端口上调出容器,这是可能的,因为Spring中的DynamicProperty ty注册表。似乎Micronaut缺少这种可能性。

文件编号:https://www.testcontainers.org/modules/gcloud/

我正在寻找在Micronaut 3中实现的JUnit5或Spock集成测试的工作示例。使用PubSubEmulatorContainer的x,如上述文档所述。

相关单据:https://micronaut-projects.github.io/micronaut-gcp/latest/guide/#emulator

GitHub上有一些关于配置TransportChannelProvider的评论。我可以注入一个实例并检查它,但我仍然没有找到确切的操作。

以下是迄今为止最接近的线索:https://github.com/micronaut-projects/micronaut-gcp/issues/257 https://github.com/micronaut-projects/micronaut-gcp/pull/259

共有1个答案

秦信瑞
2023-03-14

太长别读

我们需要首先启动testcontainer,获取模拟器主机地址,然后调用ApplicationContext。按如下方式运行:

applicationContext = ApplicationContext.run(               
["pubsub.emulator.host": emulatorHost])

小型Github repo,示例代码:https://github.com/roar-skinderviken/pubsub-emulator-demo

带代码的长答案

我终于设法使用Micronaut 3.0.2和Spock制作了一个工作解决方案。相关的Micronaut PR让我走上了正轨,还有这篇文章:Micronaut测试最佳实践https://objectcomputing.com/files/9815/9259/7089/slide_deck_Micronaut_Testing_Best_Practices_webinar.pdf

第一个PubSubEmulator类(Groovy)

package no.myproject.testframework.testcontainers

import org.testcontainers.containers.PubSubEmulatorContainer
import org.testcontainers.utility.DockerImageName

class PubSubEmulator {
    static PubSubEmulatorContainer pubSubEmulatorContainer

    static init() {
        if (pubSubEmulatorContainer == null) {
            pubSubEmulatorContainer = new PubSubEmulatorContainer(
                    DockerImageName.parse("gcr.io/google.com/cloudsdktool/cloud-sdk:emulators"))
            pubSubEmulatorContainer.start()
        }
    }
}

然后是PubSubEmulator(Groovy)的夹具

package no.myproject.testframework.testcontainers

trait PubSubEmulatorFixture {
    Map<String, Object> getPubSubConfiguration() {
        if (PubSubEmulator.pubSubEmulatorContainer == null || !PubSubEmulator.pubSubEmulatorContainer.isRunning()) {
            PubSubEmulator.init()
        }
        [
                "pubsub.emulator-host": PubSubEmulator.pubSubEmulatorContainer.getEmulatorEndpoint()
        ]
    }
}

然后,一个规范类(Groovy)启动容器,创建主题和订阅。

这里的线索是传入pubsub。模拟器。调用ApplicationContext时,将主机作为配置的一部分。运行。

代码的其余部分与我在问题中链接的Spring Boot示例非常相似。

package no.myproject.testframework

import com.google.api.gax.core.NoCredentialsProvider
import com.google.api.gax.grpc.GrpcTransportChannel
import com.google.api.gax.rpc.FixedTransportChannelProvider
import com.google.cloud.pubsub.v1.SubscriptionAdminClient
import com.google.cloud.pubsub.v1.SubscriptionAdminSettings
import com.google.cloud.pubsub.v1.TopicAdminClient
import com.google.cloud.pubsub.v1.TopicAdminSettings
import com.google.pubsub.v1.ProjectSubscriptionName
import com.google.pubsub.v1.PushConfig
import com.google.pubsub.v1.TopicName
import io.grpc.ManagedChannelBuilder
import io.micronaut.context.ApplicationContext
import no.myproject.configuration.GcpConfigProperties
import no.myproject.configuration.PubSubConfigProperties
import no.myproject.testframework.testcontainers.PubSubEmulatorFixture
import spock.lang.AutoCleanup
import spock.lang.Shared
import spock.lang.Specification

abstract class PubSubSpecification extends Specification
        implements PubSubEmulatorFixture, EnvironmentFixture {

    @AutoCleanup
    @Shared
    EmbeddedServer embeddedServer

    @AutoCleanup
    @Shared
    ApplicationContext applicationContext

    def setupSpec() {

        // start the pubsub emulator
        def emulatorHost = getPubSubConfiguration().get("pubsub.emulator-host")

        // start a temporary applicationContext in order to read config
        // keep any pubsub subscriptions out of context at this stage
        applicationContext = ApplicationContext.run()

        def gcpConfigProperties = applicationContext.getBean(GcpConfigProperties)
        def pubSubConfigProperties = applicationContext.getBean(PubSubConfigProperties)

        def channel = ManagedChannelBuilder.forTarget("dns:///" + emulatorHost)
                .usePlaintext()
                .build()

        def channelProvider =
                FixedTransportChannelProvider.create(GrpcTransportChannel.create(channel))

        // START creating topic

        def topicAdminClient =
                TopicAdminClient.create(
                        TopicAdminSettings.newBuilder()
                                .setCredentialsProvider(NoCredentialsProvider.create())
                                .setTransportChannelProvider(channelProvider)
                                .build())

        def topic = TopicName.of(
                gcpConfigProperties.getProjectId(),
                pubSubConfigProperties.getTopicName())

        try {
            topicAdminClient.createTopic(topic)
        } catch (AlreadyExistsException) {
            // this is fine, already created
            topicAdminClient.getTopic(topic)
        }

        // START creating subscription

        pubSubConfigProperties.getSubscriptionNames().forEach(it -> {
            def subscription =
                    ProjectSubscriptionName.of(gcpConfigProperties.getProjectId(), it)

            def subscriptionAdminClient =
                    SubscriptionAdminClient.create(
                            SubscriptionAdminSettings.newBuilder()
                                    .setTransportChannelProvider(channelProvider)
                                    .setCredentialsProvider(NoCredentialsProvider.create())
                                    .build())

            try {
                subscriptionAdminClient
                        .createSubscription(
                                subscription,
                                topic,
                                PushConfig.getDefaultInstance(),
                                100)

                System.out.println("Subscription created " + subscriptionAdminClient.getSubscription(subscription))
            } catch (AlreadyExistsException) {
                // this is fine, already created
                subscriptionAdminClient.getSubscription(subscription)
            }
        })

        channel.shutdown()

        // stop the temporary applicationContext
        applicationContext.stop()

        // start the actual applicationContext
        embeddedServer = ApplicationContext.run(
                EmbeddedServer,
                [
                        'spec.name'           : "PubSubEmulatorSpec",
                        "pubsub.emulator.host": emulatorHost
                ],
                environments)

        applicationContext = embeddedServer.applicationContext
    }
}

然后是用于模拟凭据的工厂类(Groovy)

package no.myproject.pubsub

import com.google.auth.oauth2.AccessToken
import com.google.auth.oauth2.GoogleCredentials
import io.micronaut.context.annotation.Factory
import io.micronaut.context.annotation.Replaces
import io.micronaut.context.annotation.Requires

import javax.inject.Singleton


@Factory
@Requires(property = 'spec.name', value = 'PubSubEmulatorSpec')
class EmptyCredentialsFactory {

    @Singleton
    @Replaces(GoogleCredentials)
    GoogleCredentials mockCredentials() {
        return GoogleCredentials.create(new AccessToken("", new Date()))
    }
}

最后,斯波克测试规范。

package no.myproject.pubsub

import no.myproject.testframework.PubSubSpecification

import java.util.stream.IntStream

class PubSubIntegrationSpec extends PubSubSpecification {

    def NUMBER_OF_MESSAGES_IN_TEST = 5
    def DELAY_IN_MILLISECONDS_PER_MSG = 100

    def "when a number of messages is sent, same amount of messages is received"() {
        given:
        def documentPublisher = applicationContext.getBean(DocumentPublisher)
        def listener = applicationContext.getBean(IncomingDocListenerWithAck)
        def initialReceiveCount = listener.getReceiveCount()

        when:
        IntStream.rangeClosed(1, NUMBER_OF_MESSAGES_IN_TEST)
                .forEach(it -> documentPublisher.send("Hello World!"))

        // wait a bit in order to let all messages propagate through the queue
        Thread.sleep(NUMBER_OF_MESSAGES_IN_TEST * DELAY_IN_MILLISECONDS_PER_MSG)

        then:
        NUMBER_OF_MESSAGES_IN_TEST == listener.getReceiveCount() - initialReceiveCount
    }
}
 类似资料:
  • 如何使用

  • 将一段文档传入BeautifulSoup 的构造方法,就能得到一个文档的对象, 可以传入一段字符串或一个文件句柄. from bs4 import BeautifulSoup soup = BeautifulSoup(open("index.html")) soup = BeautifulSoup("<html>data</html>") 首先,文档被转换成Unicode,并且HTML的实例

  • 基础运用 Redis::set('user:profile:' . $id, "Swoft"); $userDesc = Redis::get('user:profile:' . $id); 你可以通过 Redis:: 调用任何 Redis 命令。Swoft 使用魔术方法将命令传递给 Redis 服务端,因此只需传递 Redis 命令所需的参数即可。示例: Redis::set('name',

  • 引入 WeUI.css文件 利用 vue init mpvue/mpvue-quickstart my-project 初始化一个 mpvue 项目,然后在 /src/main.js 中引入 weui.css 由于是在小程序中使用,于是就直接使用了 weiui-wxss 中的样式文件,官方提供的是 weui.wxss,因此手动转成了 weui.css,然后引入即可。 这里提供 weui.css 一

  • 将一段文档传入BeautifulSoup 的构造方法,就能得到一个文档的对象, 可以传入一段字符串或一个文件句柄. from bs4 import BeautifulSoup soup = BeautifulSoup(open("index.html")) soup = BeautifulSoup("<html>data</html>") 首先,文档被转换成Unicode,并且HTML的实例

  • 目录 简介 定义资源 主流框架的默认适配 抛出异常的方式定义资源 返回布尔值方式定义资源 注解方式定义资源 异步调用支持 规则的种类 流量控制规则 熔断降级规则 系统保护规则 访问控制规则 热点规则 查询修改规则 定制规则推送方式 其它 API 业务异常统计 Tracer 上下文工具类 ContextUtil 指标统计配置 规则生效的效果 判断限流降级异常 Dashboard 实时监控 简介 Se

  • 英文原文:http://www.phpconcept.net/pclzip/user-guide/18 PKZIP 压缩包的内部表示方式 每个 PKZIP 压缩包都由一个 PclZip 对象表示。 当使用 PclZip 对象创建一个 PclZip 压缩包时,需绑定压缩包的名字。 此时,PclZip 不会检查压缩包,也不可读,甚至压缩包还不存在。 require_once('pclzip.lib.p

  • 使用步骤 使用JustAuth总共分三步(这三步也适合于JustAuth支持的任何一个平台): 申请注册第三方平台的开发者账号 创建第三方平台的应用,获取配置信息(accessKey, secretKey, redirectUri) 使用该工具实现授权登陆 使用方式 引入依赖 <dependency> <groupId>me.zhyd.oauth</groupId> <artifa