package functional.tests
import com.mongodb.client.MongoClient
import com.mongodb.client.MongoClients
import groovy.transform.CompileStatic
import org.testcontainers.containers.MongoDBContainer
@CompileStatic
trait EmbeddedMongoClient {
abstract MongoDBContainer getMongoDBContainer()
MongoClient createMongoClient() {
if (!mongoDBContainer.isRunning()) {
mongoDBContainer.start()
}
return MongoClients.create(mongoDBContainer.getReplicaSetUrl())
}
}
12 Unit Testing
Version: 2023.3.0
12 Unit Testing
To write unit tests with MongoDB and Spock you can simply extend from grails.test.mongodb.MongoSpec
.
MongoSpec
is an abstract class that will initialise GORM in the setup phase of the specification being executed. It
uses by default a MongoClient
instance that connects to a MongoDB instance as defined in your configuration
(by default, localhost
and port 27017
, see [gettingStarted] for more details):
It is preferable to use testcontainers to automatically run MongoDB in a containerized environment and not have to run a MongoDB instance locally. The following examples use testcontainers:
import grails.test.mongodb.MongoSpec
import grails.validation.ValidationException
import org.testcontainers.containers.MongoDBContainer
import org.testcontainers.utility.DockerImageName
import spock.lang.Ignore
import spock.lang.Shared
class LocalMongoUnitSpec extends MongoSpec implements EmbeddedMongoClient {
@Shared
final MongoDBContainer mongoDBContainer = new MongoDBContainer(DockerImageName.parse("mongo:latest"))
@Ignore
void "test fail on error"() {
when:
def invalid = new Book(title: "")
invalid.save()
then:
thrown ValidationException
invalid.hasErrors()
}
}
You can also use your own low-level MongoClient
instance, as shown in the following example:
package functional.tests
import grails.test.mongodb.MongoSpec
import org.testcontainers.containers.MongoDBContainer
import org.testcontainers.utility.DockerImageName
import spock.lang.Shared
class BookUnitSpec extends MongoSpec implements EmbeddedMongoClient {
@Shared
final MongoDBContainer mongoDBContainer = new MongoDBContainer(DockerImageName.parse("mongo:latest"))
void "Test low-level API extensions"() {
when:
def db = createMongoClient().getDatabase("test")
// db.drop()
// Insert a document
db['languages'].insert([name: 'Groovy'])
// A less verbose way to do it
db.languages.insert(name: 'Ruby')
// Yet another way
db.languages << [name: 'Python']
then:
db.languages.count() == 3
}
void "Test GORM access"(){
when:
Book book = new Book(title: 'El Quijote').save(flush: true)
then:
Book.count() ==1
when:
book = Book.findByTitle('El Quijote')
then:
book.id
}
}
Note that the default implementation is to scan your classpath searching for domain classes, from the package defined
in the configuration property grails.codegen.defaultPackage
, and all the way down its subpackages. If your application
is large, classpath scanning may be slow, so it’s better to override the method getDomainClasses()
:
@Override
protected List<Class> getDomainClasses() {
[Book]
}