/**mock测试要点：
 * 1）mock对象构建:注解/函数
 * 2）设定mock对象方法调用及其返回值：every{   } returns/answers
 * 3）验证方法调用行为（次数、顺序等）：verify
 * 4）断言方法调用结果：assert
 * 5）协程中的关起函数使用coEvery, coVerify, coMatch, coAssert, coRun, coAnswers or coInvoke
 */
import io.mockk.*
import io.mockk.impl.annotations.InjectMockKs
import io.mockk.impl.annotations.MockK
import io.mockk.impl.annotations.RelaxedMockK
import io.mockk.impl.annotations.SpyK
import io.mockk.junit5.MockKExtension
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith

@ExtendWith(MockKExtension::class)
class MockTests {

    //通过注解构造mock
    @MockK
    lateinit var car1: Car

    @RelaxedMockK
    lateinit var car2: Car

    @MockK(relaxUnitFun = true) //等效：@MockK(relaxed = true)
    lateinit var car3: Car

    @SpyK
    var car4 = Car("BMW")

    @InjectMockKs
    //var trafficSystem = TrafficSystem()

    @BeforeEach
    fun setUp() = MockKAnnotations.init(this, relaxUnitFun = true) // turn relaxUnitFun on for all mocks

    @Test
    fun mocktest0(@MockK calculator: Calculator, @RelaxedMockK car: Car) {
        car.drive()

        every { calculator.add(1, 3) } returns 55
        assertEquals(4, calculator.add(1, 3))
    }

    //通过函数构造mock
    @Test
    fun mockCreateTest() {
        // ... use car1, car2, car3 and car4
        val car5 = mockk<Car>()
        val car6 = mockk<Car>(relaxed = true)
        val car7 = spyk(Car("Jeep"))  //spyk<Car>()
        val car8 = mockkClass(Car::class)

        //mockK
        every { car1.drive(Direction.NORTH) } returns "car1 drive"
        assertEquals("car1 drive", car1.drive(Direction.NORTH))
        //car1.drive()    //error:no answer found for: Car(car1#1).drive()

        every { car8.drive(Direction.NORTH) } returns "car8 drive"
        assertEquals(Direction.NORTH, car8.drive(Direction.NORTH))

        //spyk
        every { car7.drive(Direction.NORTH) } returns "car7 drive"
        assertEquals("car7 drive", car7.drive(Direction.NORTH))
        assertEquals(Unit, car4.drive())

        //执行定义的行为
        car7.drive()

        //Mock Relaxed:为无返回值的方法分配默认行为
        every { car1.drive() } returns Unit
        car1.drive()
        every { car5.drive() } just Runs
        car5.drive()
        //@RelaxedMockK，只记录执行次数，不再执行原方法中定义的行为
        car6.drive()
        car2.drive()
        car3.drive()

        //一次调用参数获取
        val slot = slot<Direction>()
        every { car1.drive(capture(slot)) } returns "direction"
        assertEquals(slot.captured, Direction.NORTH)
        //多次调用参数获取
        val list = mutableListOf<Direction>()
        every { car1.drive(capture(list)) } returns "direction"
        assertEquals(list[0], Direction.NORTH)
        assertEquals(list[1], Direction.EAST)
    }

    //验证方法调用的行为（而不是结果）
    @Test
    fun verificationTest() {
        val obj = mockk<MockCls>()
        val slot = slot<Int>()

        every {
            obj.sum(any(), capture(slot))
        } answers {
            1 + firstArg<Int>() + slot.captured
        }

        obj.sum(1, 2) // returns 4
        obj.sum(1, 3) // returns 5
        obj.sum(2, 2) // returns 5

        //验证任意个数的方法调用，且不论顺序如何
        verify {
            obj.sum(2, 2)
            obj.sum(1, 3)
        }

        //验证所有方法调用，但不论顺序如何
        verifyAll {
            obj.sum(1, 3)
            obj.sum(1, 2)
            obj.sum(2, 2)
        }

        //验证所有方法调用及其顺序
        verifySequence {
            obj.sum(1, 2)
            obj.sum(1, 3)
            obj.sum(2, 2)
        }

        //验证部分方法调用及其顺序
        verifyOrder {
            obj.sum(1, 2)
            obj.sum(2, 2)
        }

        val obj2 = mockk<MockCls>()
        val obj3 = mockk<MockCls>()

        verify {
            listOf(obj2, obj3) wasNot Called
        }

        //验证该对象在被测试方法中的所有函数调用是否都被验证(verify)过了
        confirmVerified(obj)

        //验证某一方法及其对应参数被调用的次数
        verify(atLeast = 2) { obj.sum(any(), 2) }
        verify(atMost = 2) { obj.sum(1, any()) }
        verify(exactly = 1) { obj.sum(any(), 3) }

        val car = mockk<Car>()
        every { car.drive(Direction.NORTH) } returns "Outcome.OK"
        every { car.drive(Direction.SOUTH) } returns "Outcome.OK"
        excludeRecords { car.drive(Direction.SOUTH) }
        car.drive(Direction.NORTH)
        car.drive(Direction.SOUTH)
        verify {
            car.drive(Direction.NORTH)
        }
        confirmVerified(car) // car.drive(Direction.SOUTH) was excluded, so confirmation is fine with only car.drive(Direction.NORTH)


        //verify Timeout:either verification is passed or timeout is reached
        mockk<MockCls> {
            every { sum(1, 2) } returns 4
            Thread {
                Thread.sleep(2000)
                sum(1, 2)
            }.start()

            verify(timeout = 3000) { sum(1, 2) }
        }

        //Returning Unit 构造方法
        justRun { obj.sub(any(), 5) }
        every { obj.sub(any(), 5) } just Runs
        every { obj.sub(any(), 5) } returns Unit
        every { obj.sub(any(), 5) } answers { Unit }
        obj.sub(6, 5)
        obj.sub(7, 5)
        obj.sub(8, 5)
        verify {
            obj.sub(6, 5)
            obj.sub(7, 5)
            obj.sub(8, 5)
        }
    }

    //Constructor mocks：用构造器创建的新Mock对象均满足every中定义的方法及其返回值
    @Test
    fun mockConstructorTest() {
        mockkConstructor(MockCls::class)
        every { anyConstructed<MockCls>().sum(1, 2) } returns 4
        assertEquals(4, MockCls().sum(1, 2))
        verify { anyConstructed<MockCls>().sum(1, 2) }
        val m = MockCls()
        assertEquals(4, m.sum(1, 2))
        assertEquals(5, m.sum(3, 2))
    }

    //可变参数方法调用及其返回值设定
    @Test
    fun varargTest() {
        val obj = mockk<ClsWithManyMany>()
        every { obj.manyMany(5, 6, *varargAll { it == 7 }) } returns 3
        println(obj.manyMany(5, 6, 7)) // 3
        println(obj.manyMany(5, 6, 7, 7)) // 3
        println(obj.manyMany(5, 6, 7, 7, 7)) // 3

        every { obj.manyMany(5, 6, *anyVararg(), 7) } returns 4

        println(obj.manyMany(5, 6, 1, 7)) // 4
        println(obj.manyMany(5, 6, 2, 3, 7)) // 4
        println(obj.manyMany(5, 6, 4, 5, 6, 7)) // 4

        every { obj.manyMany(5, 6, *varargAny { nArgs > 5 }, 7) } returns 5

        println(obj.manyMany(5, 6, 4, 5, 6, 7)) // 5
        println(obj.manyMany(5, 6, 4, 5, 6, 7, 7)) // 5

        every {
            obj.manyMany(5, 6, *varargAny {
                if (position < 3) it == 3 else it == 4
            }, 7)
        } returns 6

        println(obj.manyMany(5, 6, 3, 4, 7)) // 6
        println(obj.manyMany(5, 6, 3, 4, 4, 7)) //6
    }

    //私有方法调用及其返回值设定
    @Test
    fun privateFunctionsMockingTest() {
        val mock = spyk<Plane>(recordPrivateCalls = true)
        every { mock["accelerate"]() } returns "going not so fast"
        assertEquals("going not so fast", mock.drive())
        verifySequence {
            mock.drive()
            mock["accelerate"]()
        }
        /*其它常用参数示例
        every { mock getProperty "speed" } returns 33
        every { mock setProperty "acceleration" value less(5) } just runs
        justRun { mock invokeNoArgs "privateMethod" }
        every { mock invoke "openDoor" withArguments listOf("left", "rear") } returns "OK"*/
    }

    //BackingField属性读写值的设定
    @Test
    fun propertyBackingFieldsTest() {
        val mock = spyk(MockCls(), recordPrivateCalls = true)
        every { mock.property } answers { fieldValue + 6 }
        every { mock.property = any() } propertyType Int::class answers { fieldValue += value }
        every { mock getProperty "property" } propertyType Int::class answers { fieldValue + 10 }
        every { mock setProperty "property" value any<Int>() } propertyType Int::class answers { fieldValue += value }
        every {
            mock.property = any()
        } propertyType Int::class answers {
            fieldValue = value + 1
        } andThen {
            fieldValue = value - 1
        }
        println(mock.property)
        mock.property = 3
        println(mock.property)
    }

    //扩展函数调用及其返回值设定
    @Test
    fun extensionFunctionsTest() {
        with(mockk<Ext>()) {//扩展函数的定义在另外的文件中时 mockkStatic("pkg.FileKt")
            every {
                Obj(5).extensionFunc()
            } returns 11
            assertEquals(11, Obj(5).extensionFunc())
            verify {
                Obj(5).extensionFunc()
            }
        }
    }

    //object mock构造
    @BeforeEach
    fun beforeTests() {
        mockkObject(MockObj)  //applies mocking to an Object
        every { MockObj.add(1, 2) } returns 55
    }

    @Test
    fun willUseMockBehaviour() {
        assertEquals(55, MockObj.add(1, 2))
    }
    //mock释放
    @AfterEach
    fun afterTests() {
        unmockkAll()
        // or unmockkObject(MockObj)
        // or clearAllMocks
    }
}

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
}

//Hierarchical mocking
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"
            }
        }
    )
}
