안녕하세요 남갯입니다.
오늘은 mockk 문서를 확인해보고자 합니다.
https://mockk.io/#constructor-mocks
MockK
Provides DSL to mock behavior. Built from zero to fit Kotlin language. Supports named parameters, object mocks, coroutines and extension function mocking
mockk.io
그래들과 메이븐 세팅 방법
Annotation
class CarTest {
@MockK
lateinit var car1: Car
@RelaxedMockK
lateinit var car2: Car
@MockK(relaxUnitFun = true)
lateinit var car3: Car
@SpyK
var car4 = Car()
@InjectMockKs
var trafficSystem = TrafficSystem()
@Before
fun setUp() = MockKAnnotations.init(this, relaxUnitFun = true) // turn relaxUnitFun on for all mocks
@Test
fun calculateAddsValues1() {
// ... use car1, car2, car3 and car4
}
}
@injectMockKs 는 기본적으로 lateinit var 혹은 var를 통해 가능하다.
다른방법을 하기위해선 overrideValues = true를 쓰거나 val을 쓰고싶다면 injectImutable = true를 사용해라. (OverrideMockKs)
JUnit4
MockAnnotations.init(this) 없이 호출 가능
class CarTest {
@get:Rule
val mockkRule = MockKRule(this)
@MockK
lateinit var car1: Car
@RelaxedMockK
lateinit var car2: Car
@Test
fun something() {
every { car1.drive() } just runs
every { car2.changeGear(any()) } returns true
// etc
}
}
JUnit5
MockKExtentsion을 사용 가능
@ExtendWith(MockKExtension::class)
class CarTest {
@MockK
lateinit var car1: Car
@RelaxedMockK
lateinit var car2: Car
@MockK(relaxUnitFun = true)
lateinit var car3: Car
@SpyK
var car4 = Car()
@Test
fun calculateAddsValues1() {
// ... use car1, car2, car3 and car4
}
}
MockK와 RelaxedMockK를 테스트 파라미터로 전송 가능하다.
@Test
fun calculateAddsValues1(@MockK car1: Car, @RelaxedMockK car2: Car) {
// ... use car1 and car2
}
Spy
실제 객체와 mock을 합치는걸 가능하게 해준다.
val car = spyk(Car()) // or spyk<Car>() to call the default constructor
car.drive(Direction.NORTH) // returns whatever the real function of Car returns
verify { car.drive(Direction.NORTH) }
confirmVerified(car)
Relaxed mock
간단한 값을 모든 함수에서 리턴해주기 때문에 각각의 케이스에 대한 동작을 따로 정의 하지 않아도 된다.
val car = mockk<Car>(relaxed = true)
car.drive(Direction.NORTH) // returns null
verify { car.drive(Direction.NORTH) }
confirmVerified(car)
제너릭한 리턴타입의 경우 relaxed가 잘 동작하지 않을 수 있음.
val func = mockk<() -> Car>(relaxed = true) // in this case invoke function has generic return type
// this line is workaround, without it the relaxed mock would throw a class cast exception on the next line
every { func() } returns Car() // or you can return mockk() for example
func()
Partial Mocking
각각의 동작처럼 사용 가능하고, answers 에 callOriginal() 로 relaxed 와 nonRelaxed형태로 둘다 동작이 가능하다.
class Adder {
fun addOne(num: Int) = num + 1
}
val adder = mockk<Adder>()
every { adder.addOne(any()) } returns -1
every { adder.addOne(3) } answers { callOriginal() }
assertEquals(-1, adder.addOne(2))
assertEquals(4, adder.addOne(3)) // original function is called
Mocking Relaxed for functions returning Unit
만약 Unit 타입을 리턴하려면 relaxUnitFun = true를 mockk의 아규먼트로 사용하거나 MockKAnnotation.init(this)에서
@MockK(relaxUnitFun = true)
lateinit var mock1: ClassBeingMocked
init {
MockKAnnotations.init(this)
}
혹은
@MockK
lateinit var mock2: ClassBeingMocked
init {
MockKAnnotations.init(this, relaxUnitFun = true)
}
요렇게 사용 가능하다.
Object mocks
이것과 같이 mockkObject로 mocking 가능
object ObjBeingMocked {
fun add(a: Int, b: Int) = a + b
}
mockkObject(ObjBeingMocked) // applies mocking to an Object
assertEquals(3, ObjBeingMocked.add(1, 2))
every { ObjBeingMocked.add(1, 2) } returns 55
assertEquals(55, ObjBeingMocked.add(1, 2))
테스트를 다 사용한 뒤 unmockkAll()을 통해 목킹 제거 가능
@Before
fun beforeTests() {
mockkObject(ObjBeingMocked)
every { MockObj.add(1,2) } returns 55
}
@Test
fun willUseMockBehaviour() {
assertEquals(55, ObjBeingMocked.add(1,2))
}
@After
fun afterTests() {
unmockkAll()
// or unmockkObject(ObjBeingMocked)
}
언어의 제한은 존재하지만 아래와같이 인스턴스 생성 가능
val newObjectMock = mockk<MockObj>()
Class mock
mockkClass를 통해 클래스 mock 가능
val car = mockkClass(Car::class)
every { car.drive(Direction.NORTH) } returns Outcome.OK
car.drive(Direction.NORTH) // returns OK
verify { car.drive(Direction.NORTH) }
EnumerationMocks
Enum을 mockkObject를 통해 mock 가능
enum class Enumeration(val goodInt: Int) {
CONSTANT(35),
OTHER_CONSTANT(45);
}
mockkObject(Enumeration.CONSTANT)
every { Enumeration.CONSTANT.goodInt } returns 42
assertEquals(42, Enumeration.CONSTANT.goodInt)
Constructor mocks
소유하지 않은 코드의 경우 새로운 객체를 생성하기 위해 사용
class MockCls {
fun add(a: Int, b: Int) = a + b
}
mockkConstructor(MockCls::class)
every { anyConstructed<MockCls>().add(1, 2) } returns 4
assertEquals(4, MockCls().add(1, 2)) // note new object is created
verify { anyConstructed<MockCls>().add(1, 2) }
생성자라 여러개일 경우 아래와 같이 생성자를 분리해서 모킹 가능하다
class MockCls(private val a: Int = 0) {
constructor(x: String) : this(x.toInt())
fun add(b: Int) = a + b
}
mockkConstructor(MockCls::class)
every { constructedWith<MockCls>().add(1) } returns 2
every {
constructedWith<MockCls>(OfTypeMatcher<String>(String::class)).add(2) // Mocks the constructor which takes a String
} returns 3
every {
constructedWith<MockCls>(EqMatcher(4)).add(any()) // Mocks the constructor which takes an Int
} returns 4
assertEquals(2, MockCls().add(1))
assertEquals(3, MockCls("2").add(2))
assertEquals(4, MockCls(4).add(7))
verify {
constructedWith<MockCls>().add(1)
constructedWith<MockCls>("2").add(2)
constructedWith<MockCls>(EqMatcher(4)).add(7)
}