본문 바로가기

카테고리 없음

[Kotlin] Mockk 문서 확인해보기

안녕하세요 남갯입니다.

 

오늘은 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)
}