-
Notifications
You must be signed in to change notification settings - Fork 694
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Testing / Mocking database #317
Comments
You are right, existing Exposed architecture is not very "mockable", but in the most cases you can replace mocking with light-weight embedded database (like H2 or SQLite). I agree that sometimes you want to use mocks, but I guess it's better to cover DAO layer interfaces with mocks. If you want to return and entities from its methods, then you can create new Entity and set |
Isn't creating test SQLite database a bit heavyweight for unit tests? |
It depends on your use-cases if you have to create a lot of tables with initial records before the test then it can take time. But you save the time which you have to spend to mocking/spying everything and you can concentrate on testing your business code which will be work the same in production (in place of working with Exposed). |
Has there been any progress over the last months on the aspect of mocking the database which Exposed is connecting to? Are there any improvements/adjustments on the Exposed Architecture to make it more mockable? |
For integration testing with MySQL we are using https://github.com/wix/wix-embedded-mysql . |
I wonder why we have to mock a lightweight database, whilst there should be the possibility to use the tools found here? Unfortunately this package is not included in gradle builds. |
well, sometimes I just want to create an instance of my DAO and test the business logic solely, and still that's hard... the only constructor require an I dont know if there's something like |
Recently faced issue related to unit test code what is executed in exposed transaction: So my solution based on mockk: import io.mockk.every
import io.mockk.mockk
import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.Transaction
import org.jetbrains.exposed.sql.transactions.TransactionManager
class TestTransactionManager : TransactionManager {
override var defaultIsolationLevel = 0
override var defaultRepetitionAttempts = 0
private val mockedDatabase: Database = mockk(relaxed = true)
override fun bindTransactionToThread(transaction: Transaction?) {
}
override fun currentOrNull(): Transaction? {
return transaction()
}
private fun transaction(): Transaction {
return mockk(relaxed = true) {
every { db } returns mockedDatabase
}
}
override fun newTransaction(isolation: Int, outerTransaction: Transaction?): Transaction {
return transaction()
}
fun apply() {
TransactionManager.registerManager(mockedDatabase, this@TestTransactionManager)
Database.connect({ mockk(relaxed = true) }, { this })
}
fun reset() {
TransactionManager.resetCurrent(null)
TransactionManager.closeAndUnregister(mockedDatabase)
}
}
val manager = TestTransactionManager()
fun mockDatabase() = manager.apply()
fun unmockDatabase() = manager.reset()
and usage on Junit5 would be like: internal class MyTest {
@BeforeEach
internal fun setUp() {
mockDatabase()
}
@AfterEach
internal fun tearDown() {
unmockDatabase()
}
} I believe it could easily translate to other mocking solutions. |
Hi there! Here is my solution for postgres. import io.zonky.test.db.postgres.embedded.EmbeddedPostgres
import org.jetbrains.exposed.dao.IntIdTable
import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.SchemaUtils
import org.jetbrains.exposed.sql.insert
import org.jetbrains.exposed.sql.transactions.transaction
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.Test
import javax.sql.DataSource
class SQLTest {
@Test
fun test() {
transaction {
// DB requests
}
}
companion object {
private val embeddedPostgres: EmbeddedPostgres = EmbeddedPostgres.start()
private val dataSource: DataSource = embeddedPostgres.postgresDatabase
@JvmStatic
@BeforeAll
fun bootstrap() {
Database.connect(dataSource)
transaction {
SchemaUtils.create(TestTable)
TestTable.insert {
it[integerValue] = 1
it[varcharValue] = "Test String"
}
}
}
@JvmStatic
@AfterAll
fun shutdown() {
embeddedPostgres.close()
}
}
}
object TestTable : IntIdTable() {
val integerValue = integer("integer_value")
val varcharValue = varchar("varchar_value", 50)
} |
I've also encountered this issue, but decided not to use mocks, as the static nature of the library makes it expensive and cumbersome to use something like MockK's Instead, I went for a simple, in-memory data base for my tests. To help stub things I made this helper class. Here's the code: /**
* A test helper that creates an in-memory database for the lifetime of the test.
*
* @property databaseName The name of the temporary database. Randomly generated by default.
* @property tables An array of tables to initialize. Will be dropped and created before each individual test.
*/
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
abstract class DatabaseTest(
private val databaseName: String = "test_db_${Random.nextInt(0, 9999)}",
private val tables: Array<Table> = emptyArray(),
) {
@Suppress("MemberVisibilityCanBePrivate")
protected val database = Database.connect("jdbc:h2:mem:$databaseName;DB_CLOSE_DELAY=-1;IGNORECASE=true;")
@BeforeEach
private fun databaseSetUp() {
transaction(database) {
SchemaUtils.drop(*tables)
SchemaUtils.create(*tables)
}
}
@AfterAll
private fun databaseTearDown() = TransactionManager.closeAndUnregister(database)
} And a usage example where class UserServiceTest : DatabaseTest("service_test_db", arrayOf(Users)) {
private val userService = UserService(database)
@Test
fun `Can identify a user by credentials`() {
User.new(1234L) { name = "John Doe", passwordHash = verySecureHash("password123") }
val actual = userService.authenticate("John Doe", "password123")
assertEquals(1234L, actual.id)
}
} Note that since the @TestInstance(TestInstance.Lifecycle.PER_CLASS)
abstract class MultiDatabaseTest(databases: Map<String, Array<Table>>) {
private val databases: Map<String, Pair<Database, Array<Table>>> =
databases
.mapValues { (databaseName, tables) ->
Database.connect("jdbc:h2:mem:$databaseName;DB_CLOSE_DELAY=-1;IGNORECASE=true;") to tables
}
protected fun database(name: String) = databases.getValue(name).first
@BeforeEach
private fun databaseSetUp() {
databases.values.forEach { (database, tables) ->
transaction(database) {
SchemaUtils.drop(*tables)
SchemaUtils.create(*tables)
}
}
}
@AfterAll
private fun databaseTearDown() =
databases.values.forEach { TransactionManager.closeAndUnregister(it.first) }
} Hope this helps. |
A simple solution using mockK
|
Working solution without any mocking frameworks.
|
I didn't want to depend specific DB and only want to focus on business logic. this is my solution without DB dependency by using mockk and I reference @guky-dhl 's code and Tapac's explanation. hope this solution helps. (I am using exposed 0.37.3 version) internal class TestTransactionManager : TransactionManager {
override var defaultIsolationLevel = 0
override var defaultRepetitionAttempts = 0
private val mockedDatabase: Database = mockk(relaxed = true)
override fun bindTransactionToThread(transaction: Transaction?) {
}
override fun currentOrNull(): Transaction {
return transaction()
}
private fun transaction(): Transaction {
return mockk(relaxed = true) {
every { db } returns mockedDatabase
}
}
override fun newTransaction(isolation: Int, outerTransaction: Transaction?): Transaction {
return transaction()
}
fun apply() {
TransactionManager.registerManager(mockedDatabase, this@TestTransactionManager)
Database.connect({ mockk(relaxed = true) }, null, { this })
}
fun reset() {
TransactionManager.resetCurrent(null)
TransactionManager.closeAndUnregister(mockedDatabase)
}
}
fun mockDatabase() = TestTransactionManager().apply() internal class RestaurantServiceTest : BehaviorSpec({
mockDatabase() // call mockTransactionManager
mockkObject(Restaurant) // exposed's Entity
val restaurantService = RestaurantService()
Given("find restaurant") {
When("by region") {
val givenRegion = "seoul"
every {Restaurant.findByRegion(givenRegion) } returns
// here is initiate mocked Entity and Restaurants is exposed's table
listOf(
Restaurant(EntityID(1, Restaurants)).apply {
this._readValues = ResultRow.createAndFillValues(
mapOf(
Restaurants.region to "seoul",
Restaurants.regionId to 1,
Restaurants.name to "delicios bbq restaurant"
// .... other fields
)
)
}
)
val restaurants = restaurantService.getByRegion(givenRegion)
Then("matched restaurant") {
assertEquals(1, restaurants.size)
assertTrue(restaurants.all { it.region == givenRegion })
}
}
}
}) |
@Tapac the approach with creating new Entity and setting class MyEntity(id: EntityID<UUID>) : UUIDEntity(id) {
companion object : UUIDEntityClass<MyEntity>(MyEntities)
var name by Migs.name
var unit by Migs.unit
val relations by MyRelationEntity referrersOn MyRelations.id
}
val myFieldIndex: Map<Expression<*>, Int> = mapOf(
MyEntities.id to 0,
MyEntities.name to 1,
MyEntities.unit to 2,
)
val myResultRowData = arrayOfNulls<Any?>(3)
myResultRowData[0]=id
myResultRowData[1]=name
myResultRowData[2]=unit
val e = MyEntity(EntityID(UUID.randomUUID(), Migs))
e._readValues = ResultRow(fieldIndex = myFieldIndex, data = myResultRowData)
// e.setRelations ??? |
I have an issue with your implementation, I get a java.lang.IllegalStateException: connection not implemented error which is from the code you did : This is when I try to read a value from my DBO Is there any better way to unit test services using repositories made with exposed ? |
I'm disappointed that it's 2022 and data access frameworks are still being created that don't have testability as a first-class feature. Please take into account principled developers who want to test their application properly. |
It's 2023, I really want to just mockk the exposed framework and don't have to create fake db. |
I'm trying to use the solution provided by greekZorba above. But I'm not using ORM/DAO in my project (i.e. no Entities), but just DSL. I.e. in my code I have something like that:
Then I'd like to
I tried several approaches, but getting out of ideas. Can someone provide an illustrative example of Unit Test based on Ideally I'd like to write the mock (for Thanks, Tomas. |
Any progress on this? |
I was running into this same issue, and the way I was able to handle this was to create a new data class which I transformed into after reading the Entity item from the (postgres) DB. I also used a TestContainers container for the DB testing. So for me, I create the So the combination of this and the
The mocking then works like this:
|
Please provide a solution for mocking Entity. |
I switched strategy after struggling with mocking DAO API entities. While they do provide an extra level of convenience than the DSL API, it is not worth the burden of not being able to easily mock them, and the subsequent inability to write simple unit tests for functions that require them. Now, I exclusively use the DSL API and manually write domain objects which can be constructed from And at last, use actual integration tests that involve real databases to test the data access layer functions. |
Are there any examples on how would one properly test code using this library, particularly mocking out the database calls?
It seems like a recommended way to use most things (database object, transactions, table objects) is global singletons, which cannot be easily mocked. Also entity classes are tightly coupled to the table singletons, making it hard to convert table to non-singleton and also making it hard to create dummy entity objects with fake data.
The text was updated successfully, but these errors were encountered: