[Kotlin] Mockk 문서 확인해보기
안녕하세요 남갯입니다.
오늘은 mockk 문서를 확인해보고자 합니다.
https://mockk.io/#constructor-mocks
그래들과 메이븐 세팅 방법
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)
}