在Spring Boot中使用Flapdoodle嵌入式MongoDB测试@Transactional

7

我希望确保 @Transactional 注解能够正常工作,因此我编写了一个测试来保存和发布文章。我的 Kafka 发布者是一个模拟器,会在任何调用时抛出异常。我希望确保 MongoDB 回滚已持久化的文章。

@Test
void testRollbackOnPublishFail() {
    when(producer.publishArticle(any())).thenThrow(IllegalStateException.class);
    ArticleDocument articleDocument = ArticleTestDataUtil.createArticleDocument();
    try {
        ArticleDocument publishedDocument = articleService.saveAndPublish(articleDocument);
    } catch (Exception e) {
        assertTrue(e instanceof IllegalStateException);
    }
    assertFalse(articleService.findById(articleDocument.getId()).isPresent());
}

我正在使用Flapdoodle的嵌入式MongoDB进行集成测试。
 testCompile "de.flapdoodle.embed:de.flapdoodle.embed.mongo:2.2.0"

这个测试失败是因为默认情况下没有开启事务/复制。

因此,通过创建MongoTransactionManager来激活事务:

@Configuration
public class MongoTransactionConfig {

    @Bean
    public MongoTransactionManager transactionManager(MongoDbFactory dbFactory) {
        return new MongoTransactionManager(dbFactory);
    }
}

现在我的测试失败了,因为无法在MongoClient中启动会话。

com.mongodb.MongoClientException: Sessions are not supported by the MongoDB cluster to which this client is connected
        at com.mongodb.MongoClient.startSession(MongoClient.java:560)

我也尝试创建自定义IMongodConfig

@Bean(name = "customReplicaMongodConfig")
    public IMongodConfig mongodConfig(EmbeddedMongoProperties embeddedProperties) throws IOException {
        Storage storage = new Storage("/tmp", "rs0", 0);
        return new MongodConfigBuilder()
                .shardServer(true)
                .version(Version.V4_0_2)
                .net(new Net(27117, Network.localhostIsIPv6()))
                .replication(storage)
                .cmdOptions(new MongoCmdOptionsBuilder().useNoJournal(false).build()).build();
    }

并开始复制:

@ConditionalOnBean(name = "customReplicaMongodConfig")
@Configuration
public class ReplicaConfig {

    @Inject
    private MongoClient mongoClient;

    @PostConstruct
    public void initiateReplicationSet() {
        mongoClient.getDatabase("admin").runCommand(new Document("replSetInitiate", new Document()));
    }
}

但 replSetInitiate 超时失败。

所以我的问题是是否可能使用嵌入式 MongoDB 创建运行中的复制集来测试事务性。

1个回答

2

您可以在此处找到有关创建副本集的信息

我的 Kotlin 解决方案:

import com.mongodb.BasicDBList
import com.mongodb.BasicDBObjectBuilder
import com.mongodb.DBObject
import com.mongodb.client.MongoClient
import com.mongodb.client.MongoClients
import com.mongodb.client.MongoCollection
import com.mongodb.client.MongoDatabase
import de.flapdoodle.embed.mongo.MongodExecutable
import de.flapdoodle.embed.mongo.MongodProcess
import de.flapdoodle.embed.mongo.MongodStarter
import de.flapdoodle.embed.mongo.config.MongoCmdOptionsBuilder
import de.flapdoodle.embed.mongo.config.MongodConfigBuilder
import de.flapdoodle.embed.mongo.config.Net
import de.flapdoodle.embed.mongo.distribution.Version
import de.flapdoodle.embed.process.runtime.Network
import org.assertj.core.api.Assertions.assertThat
import org.bson.Document
import org.junit.jupiter.api.Test
import org.springframework.data.mongodb.MongoDatabaseFactory
import org.springframework.data.mongodb.MongoTransactionManager
import org.springframework.data.mongodb.core.MongoTemplate
import org.springframework.data.mongodb.core.SimpleMongoClientDatabaseFactory
import org.springframework.test.context.ActiveProfiles
import org.springframework.transaction.TransactionStatus
import org.springframework.transaction.support.TransactionCallbackWithoutResult
import org.springframework.transaction.support.TransactionTemplate
import java.io.IOException


@ActiveProfiles("test")

class EmbeddedMongoDbTransactionTest {
    private val CONNECTION_STRING = "mongodb://%s:%d/"  

    private var node1MongodExe: MongodExecutable? = null
    private var node1Mongod: MongodProcess? = null
    private var mongo: MongoClient? = null
    private var node2MongodExe: MongodExecutable? = null
    private var node2Mongod: MongodProcess? = null

    @Test
    @Throws(IOException::class)
    fun testSmth() {
        val runtime = MongodStarter.getDefaultInstance()
        val node1Port = 57023
        val node2Port = 57024
        try {
            node1MongodExe = runtime.prepare(
                MongodConfigBuilder().version(Version.Main.PRODUCTION)
                    .withLaunchArgument("--replSet", "rs0")
                    .cmdOptions(MongoCmdOptionsBuilder().useNoJournal(false).build())
                    .net(Net(node1Port, Network.localhostIsIPv6())).build()
            )
            node1Mongod = node1MongodExe?.start()
            node2MongodExe = runtime.prepare(
                MongodConfigBuilder().version(Version.Main.PRODUCTION)
                    .withLaunchArgument("--replSet", "rs0")
                    .cmdOptions(MongoCmdOptionsBuilder().useNoJournal(false).build())
                    .net(Net(node2Port, Network.localhostIsIPv6())).build()
            )
            node2Mongod = node2MongodExe?.start()
            mongo = MongoClients.create(CONNECTION_STRING.format("localhost", node1Port))
            val adminDatabase: MongoDatabase = mongo!!.getDatabase("admin")

            val config = Document("_id", "rs0")
            val members = BasicDBList()
            members.add(Document("_id", 0).append("host", "localhost:$node1Port"))
            members.add(Document("_id", 1).append("host", "localhost:$node2Port"))
            config.put("members", members)

            adminDatabase.runCommand(Document("replSetInitiate", config))

            println(">>>>>> wait")
            println(">>>>>>>>" + adminDatabase.runCommand(Document("replSetGetStatus", 1)))
            Thread.sleep(15_000) // without waiting fails with error : 'not master' on server

            val funDb: MongoDatabase = mongo?.getDatabase("fun")!!

            // insert test 1
            val testCollection: MongoCollection<Document> = funDb.getCollection("test")
            println(">>>>>>>> inserting data")
            testCollection.insertOne(Document("fancy", "value"))
            println(">>>>>>>> finding data")
            assertThat(testCollection.find().first()!!.get("fancy")).isEqualTo("value")


            // insert test 2 (with transaction)
            val mongoTemplate = MongoTemplate(mongo!!, "test")

            // Without creating collection in advance fails with error:
            // Cannot create namespace in multi-document transaction
            // (https://dev59.com/pFQK5IYBdhLWcg3wDLgw)
            mongoTemplate.createCollection("collection")

            val mongoDatabaseFactory: MongoDatabaseFactory = SimpleMongoClientDatabaseFactory(mongo!!, "test")
            val mongoTransactionManager = MongoTransactionManager(mongoDatabaseFactory)

            val transactionTemplate = TransactionTemplate(mongoTransactionManager)

            transactionTemplate.execute(object : TransactionCallbackWithoutResult() {
                override fun doInTransactionWithoutResult(status: TransactionStatus) {
                    val objectToSave = BasicDBObjectBuilder.start()
                        .add("key", "value")
                        .get()

                    // when
                    mongoTemplate.save(objectToSave, "collection")

                    // then
                    assertThat(mongoTemplate.findAll(DBObject::class.java, "collection"))
                        .extracting("key")
                        .containsOnly("value")
                }
            })

            // after transaction
            assertThat(mongoTemplate.findAll(DBObject::class.java, "collection"))
                .extracting("key")
                .containsOnly("value")

        } finally {
            println(">>>>>> shutting down")
            mongo?.close()
            node1MongodExe?.stop()
            node1Mongod?.stop()
            node2MongodExe?.stop()
            node2Mongod?.stop()
        }
    }
}

好的。非常感谢!我会尝试在我们的项目中采用这个解决方案。 - DCO

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接