반응형
dependencies
dependencies {
...
testImplementation("io.mockk:mockk:1.13.4")
}
mockk basic
import io.kotest.core.spec.IsolationMode
import io.kotest.core.spec.style.FunSpec
import io.mockk.*
class MyRepository {
fun save(text: String) {
println(text)
}
}
class MyService(private val myRepository: MyRepository) {
fun save(text: String) {
myRepository.save(text)
}
}
class MyTests : FunSpec({
// repository는 singleton이기 때문에 올바르게 테스트하기 위해서는 clearMocks을 해주거나 isolationMode를 세팅해주어야함
// afterTest { clearMocks(repository) }
isolationMode = IsolationMode.InstancePerTest
// required : testImplementation("io.mockk:mockk:1.13.4")
val repository = mockk<MyRepository>() // = mockkClass(MyRepository::class)
val service = MyService(repository)
test("Saves to repository") {
every { repository.save(any()) } just Runs
service.save("a")
verify(exactly = 1) { repository.save("a") }
}
test("Saves to repository 2") {
every { repository.save(any()) } just Runs
service.save("a")
verify(exactly = 1) { repository.save("a") }
}
})
relaxed mock
import io.kotest.core.spec.IsolationMode
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.mockk.*
class Adder {
fun addOne(num: Int) = num + 1
}
class MyTests : FunSpec({
isolationMode = IsolationMode.InstancePerTest
val adder = mockk<Adder>(relaxed = true) // mocking 설정 없어도 오류 발생하지 않는 설정
test("test mockk") {
// every { adder.addOne(any()) } returns -1
adder.addOne(3) shouldBe 0
verify(exactly = 1) { adder.addOne(3) }
}
})
spy
import io.kotest.core.spec.IsolationMode
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.mockk.*
class Adder {
fun addOne(num: Int) = num + 1
}
class MyTests : FunSpec({
isolationMode = IsolationMode.InstancePerTest
val adder = spyk(Adder())
test("test mockk") {
adder.addOne(3) shouldBe 4 // 실제 함수 호출
every { adder.addOne(any()) } returns -1
adder.addOne(3) shouldBe -1 // 모킹 함수 호출
verify(exactly = 2) { adder.addOne(3) }
}
})
partial mocking
import io.kotest.core.spec.IsolationMode
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.mockk.*
class Adder {
fun addOne(num: Int) = num + 1
}
class MyTests : FunSpec({
isolationMode = IsolationMode.InstancePerTest
val adder = mockk<Adder>()
test("test mockk") {
every { adder.addOne(any()) } returns -1
every { adder.addOne(3) } answers { callOriginal() }
adder.addOne(2) shouldBe -1
adder.addOne(3) shouldBe 4 // original function is called
}
})
object mocks
import io.kotest.core.spec.IsolationMode
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.mockk.*
class Adder {
fun addOne(num: Int) = num + 1
}
class MyTests : FunSpec({
isolationMode = IsolationMode.InstancePerTest
val adder = Adder()
test("test mockk") {
mockkObject(adder) // applies mocking to an object
adder.addOne(3) shouldBe 4 // original function is called
every { adder.addOne(any()) } returns -1
adder.addOne(2) shouldBe -1 // mocking function is called
}
})
enum mocks
import io.kotest.core.spec.IsolationMode
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.mockk.*
enum class Status(val code: Int) {
SUCCESS(0),
FAIL(-1)
}
class MyTests : FunSpec({
isolationMode = IsolationMode.InstancePerTest
test("test mockk") {
mockkObject(Status.SUCCESS)
every { Status.SUCCESS.code } returns 100
Status.SUCCESS.code shouldBe 100
unmockkObject(Status.SUCCESS) // or unmockkAll()
}
})
constructor mocks
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.mockk.*
class Adder {
fun addOne(num: Int) = num + 1
}
class MyTests : FunSpec({
test("test mockk") {
mockkConstructor(Adder::class)
every { anyConstructed<Adder>().addOne(any()) } returns -1
Adder().addOne(3) shouldBe -1
unmockkConstructor(Adder::class)
}
})
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.mockk.*
class Adder(private val offset: Int) {
constructor(offset: String) : this(offset.toInt())
fun add(b: Int) = offset + b
}
class MyTests : FunSpec({
test("test mockk") {
mockkConstructor(Adder::class)
every { constructedWith<Adder>(EqMatcher(1)).add(any()) } returns -1
every { constructedWith<Adder>(OfTypeMatcher<String>(String::class)).add(any()) } returns -2
Adder(1).add(1) shouldBe -1
Adder("2").add(1) shouldBe -2
verify(exactly = 1) {
constructedWith<Adder>(EqMatcher(1)).add(1)
constructedWith<Adder>(OfTypeMatcher<String>(String::class)).add(1)
}
unmockkConstructor(Adder::class)
}
})
partial argument matching
import io.kotest.core.spec.IsolationMode
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.mockk.*
class Adder {
fun add(a: Int, b: Int) = a + b
}
class MyTests : FunSpec({
isolationMode = IsolationMode.InstancePerTest
val adder = mockk<Adder>()
test("test mockk") {
every { adder.add(any(), 5) } returns 10
every { adder.add(any(), more(10)) } returns 100
adder.add(1, 5) shouldBe 10
adder.add(1, 15) shouldBe 100
verify(exactly = 1) {
adder.add(1, 5)
adder.add(1, 15)
}
}
})
chained mocking
import io.kotest.core.spec.IsolationMode
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.mockk.*
class Status {
fun isActive() = true
}
class User {
fun getStatus() = Status()
}
class MyTests : FunSpec({
isolationMode = IsolationMode.InstancePerTest
val user = mockk<User>()
test("test mockk") {
every { user.getStatus().isActive() } returns false // chained mocking
user.getStatus().isActive() shouldBe false
verify(exactly = 1) { user.getStatus().isActive() }
}
})
hierarchical mocking
import io.kotest.core.spec.IsolationMode
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.mockk.*
interface AddressBook {
val contacts: List<Contact>
}
interface Contact {
val name: String
val telephone: String
val address: Address
}
interface Address {
val city: String
val zip: String
}
class MyTests : FunSpec({
isolationMode = IsolationMode.InstancePerTest
val addressBook = mockk<AddressBook> {
every { contacts } returns listOf(
mockk {
every { name } returns "John"
every { telephone } returns "123-456-789"
every { address.city } returns "New-York"
every { address.zip } returns "123-45"
},
mockk {
every { name } returns "Alex"
every { telephone } returns "789-456-123"
every { address } returns mockk {
every { city } returns "Wroclaw"
every { zip } returns "543-21"
}
}
)
}
test("test mockk") {
addressBook.contacts[0].address.city shouldBe "New-York"
addressBook.contacts[1].address.city shouldBe "Wroclaw"
}
})
capture
import io.kotest.core.spec.IsolationMode
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.mockk.*
class Adder {
fun add(a: Int, b: Int) = a + b
}
class MyTests : FunSpec({
isolationMode = IsolationMode.InstancePerTest
val slot = slot<Int>()
val list = mutableListOf<Int>()
val adder = mockk<Adder>()
test("test mockk") {
every { adder.add(1, capture(slot)) } answers { 100 }
every { adder.add(2, capture(list)) } answers { 200 }
adder.add(1, 10) shouldBe 100
adder.add(2, 20) shouldBe 200
slot.captured shouldBe 10
list.first() shouldBe 20
verify(exactly = 2) { adder.add(or(1, 2), any()) }
}
})
verify
import io.kotest.core.spec.IsolationMode
import io.kotest.core.spec.style.FunSpec
import io.mockk.*
class Adder {
fun add(a: Int, b: Int) = a + b
}
class MyTests : FunSpec({
isolationMode = IsolationMode.InstancePerTest
val adder = mockk<Adder>(relaxed = true)
test("test mockk") {
adder.add(1, 1)
adder.add(1, 2)
adder.add(3, 3)
verify(atLeast = 3) { adder.add(any(), any()) }
verify(atMost = 2) { adder.add(1, or(1, 2)) }
verify(exactly = 1) { adder.add(3, 3) }
verify(exactly = 0) { adder.add(100, 100) }
}
})
import io.kotest.core.spec.IsolationMode
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.mockk.*
class Adder {
fun add(a: Int, b: Int) = a + b
}
class MyTests : FunSpec({
isolationMode = IsolationMode.InstancePerTest
val adder = mockk<Adder>()
val slot = slot<Int>()
test("test mockk") {
every { adder.add(any(), capture(slot)) } answers { 1 + firstArg<Int>() + slot.captured }
adder.add(1, 2) shouldBe 4
adder.add(1, 3) shouldBe 5
adder.add(2, 2) shouldBe 5
// 모두 호출했는지 체크 (순서 상관X)
verifyAll {
adder.add(2, 2)
adder.add(1, 3)
adder.add(1, 2)
}
// 모두 호출했는지 체크 (순서 상관O)
verifySequence {
adder.add(1, 2)
adder.add(1, 3)
adder.add(2, 2)
}
// 호출 순서가 맞는지 체크
verifyOrder {
adder.add(1, 2)
// adder.add(1, 3)
adder.add(2, 2)
}
val adder2 = mockk<Adder>()
val adder3 = mockk<Adder>()
verify { listOf(adder2, adder3) wasNot Called }
}
})
confirmVerified
import io.kotest.core.spec.IsolationMode
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.mockk.*
class Adder {
fun add(a: Int, b: Int) = a + b
}
class MyTests : FunSpec({
isolationMode = IsolationMode.InstancePerTest
val adder = mockk<Adder>()
test("test mockk") {
every { adder.add(1, 1) } returns -1
every { adder.add(2, 2) } returns -2
adder.add(1, 1) shouldBe -1
adder.add(2, 2) shouldBe -2
verify {
adder.add(1, 1)
adder.add(2, 2)
}
confirmVerified(adder) // 호출한 mocking 메소드를 모두 verify에서 검증했는지를 체크
}
})
import io.kotest.core.spec.IsolationMode
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.mockk.*
class Adder {
fun add(a: Int, b: Int) = a + b
}
class MyTests : FunSpec({
isolationMode = IsolationMode.InstancePerTest
val adder = mockk<Adder>()
test("test mockk") {
every { adder.add(1, 1) } returns -1
every { adder.add(2, 2) } returns -2
// 해당 mocking method 호출하고 verify에서 검증하지 않아도
// confirmVerified에서 체크시 오류 발생하지 않도록 exclude 처리
excludeRecords { adder.add(2, 2) }
adder.add(1, 1) shouldBe -1
adder.add(2, 2) shouldBe -2
verify {
adder.add(1, 1)
}
confirmVerified(adder)
}
})
timeout test
import io.kotest.core.spec.IsolationMode
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.mockk.*
class Adder {
fun add(a: Int, b: Int) = a + b
}
class MyTests : FunSpec({
isolationMode = IsolationMode.InstancePerTest
val adder = mockk<Adder>()
test("test mockk") {
every { adder.add(1, 1) } returns -1
Thread {
Thread.sleep(2000)
adder.add(1, 1) shouldBe -1
}.start()
verify(timeout = 3000) { adder.add(1, 1) }
}
})
Unit return test
import io.kotest.core.spec.IsolationMode
import io.kotest.core.spec.style.FunSpec
import io.mockk.*
class Adder {
fun add(a: Int, b: Int) = println(a + b)
}
class MyTests : FunSpec({
isolationMode = IsolationMode.InstancePerTest
val adder = mockk<Adder>()
test("test mockk") {
// justRun { adder.add(any(), any()) }
every { adder.add(any(), any()) } just Runs
adder.add(1, 1)
adder.add(2, 2)
verifySequence {
adder.add(1, 1)
adder.add(2, 2)
}
}
})
extension function test
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.mockk.*
data class User(val status: Int)
class Ext {
fun User.isActive() = status == 1
}
class MyTests : FunSpec({
test("test mockk") {
with(mockk<Ext>()) {
every { User(100).isActive() } returns true
User(100).isActive() shouldBe true
verify { User(100).isActive() }
}
}
})
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.mockk.*
data class User(val status: Int)
fun User.isActive() = status == 1
class MyTests : FunSpec({
test("test mockk") {
mockkStatic(User::isActive)
every { User(100).isActive() } returns true
User(100).isActive() shouldBe true
verify { User(100).isActive() }
unmockkStatic(User::isActive)
}
})
static method mocking test
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.mockk.*
import org.apache.commons.lang3.StringUtils
object DemoUtil {
fun getMessage() = "Hello World"
}
class MyTests : FunSpec({
test("test mockk") {
mockkObject(DemoUtil) {
every { DemoUtil.getMessage() } returns "Hi"
DemoUtil.getMessage() shouldBe "Hi"
verify { DemoUtil.getMessage() }
}
}
test("test mockk 2") {
mockkStatic(StringUtils::class) {
every { StringUtils.length(any()) } returns 5
StringUtils.length("12345678") shouldBe 5
}
}
})
vararg test
import io.kotest.core.spec.IsolationMode
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.mockk.*
interface ManyMany {
fun manymany(vararg x: Any): Int
}
class MyTests : FunSpec({
isolationMode = IsolationMode.InstancePerLeaf
val obj = mockk<ManyMany>()
test("test mockk") {
every { obj.manymany(5, 6, *varargAll { it == 7 }) } returns 1
obj.manymany(5, 6, 7) shouldBe 1
obj.manymany(5, 6, 7, 7) shouldBe 1
obj.manymany(5, 6, 7, 7, 7) shouldBe 1
every { obj.manymany(5, 6, *anyVararg(), 7) } returns 2
obj.manymany(5, 6, 7) shouldBe 2
obj.manymany(5, 6, 2, 3, 7) shouldBe 2
obj.manymany(5, 6, 4, 5, 6, 7) shouldBe 2
every { obj.manymany(5, 6, *varargAny { nArgs > 5 }, 7) } returns 3
obj.manymany(5, 6, 6, 1, 3, 7) shouldBe 3
obj.manymany(5, 6, 6, 7, 2, 8, 7) shouldBe 3
every { obj.manymany(5, 6, *varargAny { if (position < 3) it == 3 else it == 4 }, 7) } returns 4
obj.manymany(5, 6, 3, 4, 7) shouldBe 4
obj.manymany(5, 6, 3, 4, 4, 7) shouldBe 4
}
})
private function mocking
import io.kotest.core.spec.IsolationMode
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.mockk.*
class Car {
fun drive(id: Int) = accelerate(id)
private fun accelerate(id: Int) = "going faster - $id"
}
class MyTests : FunSpec({
isolationMode = IsolationMode.InstancePerLeaf
val car = spyk<Car>(recordPrivateCalls = true)
test("test mockk") {
every { car["accelerate"](any<Int>()) } returns "going not so fast"
car.drive(1) shouldBe "going not so fast"
verifySequence {
car.drive(1)
car["accelerate"](1)
}
}
})
match
interface Service {
fun process(value: Int): String
}
class MyTests : FunSpec({
isolationMode = IsolationMode.InstancePerLeaf
test("match를 활용한 테스트") {
val mockService = mockk<Service>()
every { mockService.process(match { it > 0 }) } returns "Positive"
every { mockService.process(match { it < 0 }) } returns "Negative"
mockService.process(5) shouldBe "Positive"
mockService.process(-5) shouldBe "Negative"
verifySequence {
mockService.process(match { it == 5 })
mockService.process(match { it == -5 })
}
}
})
참고
반응형
'Development > Kotest' 카테고리의 다른 글
[Kotest] Testcontainers (0) | 2023.11.04 |
---|---|
[Kotest] SpringBootTest (0) | 2023.11.04 |
[Kotest] extensions (0) | 2023.11.04 |
[Kotest] lifecycle hook (0) | 2023.11.04 |
[Kotest] isolation modes (0) | 2023.11.04 |