Couchbase¶
Configure¶
After getting the library from the maven source, while configuring TestSystem you will have access to couchbase
function.
This function configures the Couchbase Docker container that is going to be started.
Here you can define a defaultBucket name.
Warning
Make sure that your application has the same bucket names.
TestSystem()
.with {
couchbase {
CouchbaseSystemOptions(defaultBucket = "test-bucket", configureExposedConfiguration = { cfg ->
listOf(
"couchbase.hosts=${cfg.hostsWithPort}",
"couchbase.username=${cfg.username}",
"couchbase.password=${cfg.password}"
)
})
}
}
.run()
Stove exposes configuration that is generated by the execution, so you can pass the real connection strings and configurations to your Spring application before it starts. Your application will start with the physical dependencies that are spun-up by the framework.
Migrations¶
Stove provides a way to run migrations before the test starts.
class CouchbaseMigration : DatabaseMigration<Cluster> {
override val order: Int = 1
override suspend fun execute(connection: Cluster) {
val bucket = connection.bucket(CollectionConstants.BUCKET_NAME)
listOf(CollectionConstants.PRODUCT_COLLECTION).forEach { collection ->
bucket.collections.createCollection(bucket.defaultScope().name, collection)
}
connection.waitUntilReady(30.seconds)
}
}
You can define your migration class by implementing the DatabaseMigration interface. You can define the order of the
migration by overriding the order property. The migrations will be executed in the order of the order property.
After defining your migration class, you can pass it to the migrations function of the couchbase configuration.
TestSystem()
.with {
couchbase {
CouchbaseSystemOptions(defaultBucket = "test-bucket", configureExposedConfiguration = { cfg ->
listOf(
"couchbase.hosts=${cfg.hostsWithPort}",
"couchbase.username=${cfg.username}",
"couchbase.password=${cfg.password}"
)
}).migrations {
register<CouchbaseMigration>()
}
}
}
.run()
Usage¶
Saving Documents¶
Save documents to Couchbase collections:
TestSystem.validate {
couchbase {
// Save to default collection (_default)
saveToDefaultCollection(
id = "user:123",
instance = User(id = "123", name = "John Doe", email = "john@example.com")
)
// Save to a specific collection
save(
collection = "products",
id = "product:456",
instance = Product(id = "456", name = "Laptop", price = 999.99)
)
}
}
Getting Documents¶
Retrieve and validate documents:
TestSystem.validate {
couchbase {
// Get from default collection
shouldGet<User>("user:123") { user ->
user.id shouldBe "123"
user.name shouldBe "John Doe"
user.email shouldBe "john@example.com"
}
// Get from specific collection
shouldGet<Product>("products", "product:456") { product ->
product.id shouldBe "456"
product.name shouldBe "Laptop"
product.price shouldBe 999.99
}
}
}
Checking Non-Existence¶
Verify that documents don't exist:
TestSystem.validate {
couchbase {
// Check default collection
shouldNotExist("user:999")
// Check specific collection
shouldNotExist("products", "product:999")
}
}
Deleting Documents¶
Delete documents and verify deletion:
TestSystem.validate {
couchbase {
// Delete from default collection
shouldDelete("user:123")
shouldNotExist("user:123")
// Delete from specific collection
shouldDelete("products", "product:456")
shouldNotExist("products", "product:456")
}
}
N1QL Queries¶
Execute N1QL queries and validate results:
TestSystem.validate {
couchbase {
// Simple query
shouldQuery<User>("SELECT u.* FROM `users` u WHERE u.age > 18") { users ->
users.size shouldBeGreaterThan 0
users.all { it.age > 18 } shouldBe true
}
// Query with multiple conditions
shouldQuery<Product>(
"""
SELECT p.*
FROM `products` p
WHERE p.price > 100 AND p.category = 'Electronics'
""".trimIndent()
) { products ->
products.size shouldBeGreaterThan 0
products.all { it.price > 100 && it.category == "Electronics" } shouldBe true
}
}
}
Working with Collections and Scopes¶
Access bucket, collection, and cluster directly:
TestSystem.validate {
couchbase {
// Access the cluster
val cluster = cluster()
// Access the bucket
val bucket = bucket()
// Perform custom operations
val customResult = bucket.collections.getAllScopes()
customResult shouldNotBe null
}
}
Pause and Unpause Container¶
Control the Couchbase container for testing failure scenarios:
TestSystem.validate {
couchbase {
// Pause the container
pause()
// Your application should handle the failure
// ...
// Unpause the container
unpause()
// Verify recovery
shouldGet<User>("user:123") { user ->
user.id shouldBe "123"
}
}
}
Complete Example¶
Here's a complete end-to-end test combining HTTP, Couchbase, and Kafka:
test("should create product and store in couchbase") {
TestSystem.validate {
val productId = UUID.randomUUID().toString()
val productName = "Gaming Laptop"
val categoryId = 1
// Mock external service
wiremock {
mockGet(
url = "/categories/$categoryId",
statusCode = 200,
responseBody = Category(id = categoryId, name = "Electronics", active = true).some()
)
}
// Create product via API
http {
postAndExpectBody<Any>(
uri = "/products",
body = ProductCreateRequest(
name = productName,
price = 1299.99,
categoryId = categoryId
).some()
) { response ->
response.status shouldBe 200
}
}
// Verify stored in Couchbase
couchbase {
shouldGet<Product>("products", "product:$productId") { product ->
product.id shouldBe productId
product.name shouldBe productName
product.price shouldBe 1299.99
product.categoryId shouldBe categoryId
}
}
// Verify event was published
kafka {
shouldBePublished<ProductCreatedEvent>(atLeastIn = 10.seconds) {
actual.id == productId &&
actual.name == productName &&
actual.price == 1299.99
}
}
// Query products by category
couchbase {
shouldQuery<Product>(
"""
SELECT p.*
FROM `products` p
WHERE p.categoryId = $categoryId
""".trimIndent()
) { products ->
products.size shouldBeGreaterThan 0
products.any { it.id == productId } shouldBe true
}
}
}
}
Integration with Application¶
Verify application behavior using the bridge:
test("should use repository to save product") {
TestSystem.validate {
val productId = UUID.randomUUID().toString()
val product = Product(id = productId, name = "Test Product", price = 99.99)
// Use application's repository
using<ProductRepository> {
save(product)
}
// Verify in Couchbase
couchbase {
shouldGet<Product>("products", "product:$productId") { savedProduct ->
savedProduct.id shouldBe productId
savedProduct.name shouldBe "Test Product"
savedProduct.price shouldBe 99.99
}
}
}
}
Advanced Operations¶
Batch Operations¶
TestSystem.validate {
couchbase {
// Save multiple documents
val users = listOf(
User(id = "1", name = "Alice"),
User(id = "2", name = "Bob"),
User(id = "3", name = "Charlie")
)
users.forEach { user ->
saveToDefaultCollection("user:${user.id}", user)
}
// Query all
shouldQuery<User>("SELECT u.* FROM `${bucket().name}` u") { result ->
result.size shouldBeGreaterThanOrEqual users.size
}
// Verify each
users.forEach { user ->
shouldGet<User>("user:${user.id}") { actual ->
actual.name shouldBe user.name
}
}
}
}
Error Handling¶
TestSystem.validate {
couchbase {
// Document not found
shouldNotExist("non-existent:key")
// Attempting to delete non-existent document throws exception
assertThrows<DocumentNotFoundException> {
shouldDelete("non-existent:key")
}
// Attempting to assert non-existence on existing document throws assertion error
saveToDefaultCollection("user:123", User(id = "123", name = "John"))
assertThrows<AssertionError> {
shouldNotExist("user:123")
}
}
}