What is unit testing?
A unit test is a piece of automated code that calls the unit of work being tested and then tests some assumptions about the single final result of the unit.
Definition of Mock
mock is to create a false object of a class and replace the real object in the test environment to achieve two purposes:
- 1. Verify the calling of some methods of this object, how many times they have been called, what the parameters are, etc
- 2. Specify the behavior of some methods of this object, return specific values, or perform specific actions
Introduction to MockK
mockk is a Mocking framework written by Kotlin. Through mockk < > (), the object returned by mockkobject(), spyk(), is in the mock state. Only objects in this state can mock the behavior of objects through every{}. mockk framework follows the process of mock - Listen - execute - verify.
import io.mockk.*
for instance
public class ServiceImplA implements Service { @Override public void doSomething1(String s) { log.info("input is:{}", s); } @Override public String doSomething2(String s) { log.info("doSomething2,input:{}", s); return s; } }
mock common object
Mock an object with the statement mock (...)
This method returns an instance of T. all functions of this instance are in the state of pending mock. These functions in the state of pending mock cannot be called directly. They can be called only after combining the corresponding method of mock with the every {} statement
//Returns a mock object for ServiceImplA val mockk = mockk<ServiceImplA>() //mock specifies the method every { mockk.doSomething1(any()) } returns Unit //Call the method that is mock mockk.doSomething1("") //If this method fails to mock through every, an error will be reported mockk.doSomething2("")
every {...} statement is used to listen to the specified code statement and take the following actions, for example:
- return value returns a value
- just Runs continues execution (for Unit methods only)
- answer {} executes a statement block
When the method in every {...} is executed in the test code, it does not really execute, but directly returns the object after returns. You can also skip execution using every {} just runs without returns.
mockk Object class
Turns the specified object into a mock able state
The difference from mockk < > () is that the returned mock object allows the mock behavior to coexist with the real behavior. If you do not actively mock, the real behavior will be executed.
val serviceImplA = ServiceImplA() mockkObject(serviceImplA) every { serviceImplA.doSomething1(any()) } returns Unit //Called mock method serviceImplA.doSomething1("sfas") //Call real method serviceImplA.doSomething2("sfas")
If you want to verify and execute the private methods in the object class, you need to specify a value recordPrivateCalls during mock, which is false by default:
mockkObject(serviceImplA, recordPrivateCalls = true)
mock static class
mockkStatic(serviceImplA::class)
spyk() & spyk(T obj)
Returns the spyk object of T or the spyk object of obj
The difference from mockk < > () is that the object returned by spyk < > () allows real behavior to coexist with mock behavior, and its performance is similar to mockobject ()
//Returns a spyk object of ServiceImplA val spyk = spyk<ServiceImplA>() every { spyk.doSomething1(any()) } returns Unit //Call mock method spyk.doSomething1("123") //Call real method spyk.doSomething2("999")
val serviceImplA = ServiceImplA() //Returns the object after the serviceImplA object is spyk. The original object will not be changed val spyk1 = spyk(serviceImplA) //serviceImplA is not in mock status. An error will be reported here //every { serviceImplA.doSomething1(any()) } returns Unit //mock every { spyk1.doSomething1(any()) } returns Unit //Call mock method spyk1.doSomething1("999") //Call real method spyk1.doSomething2("999")
returns
Action is the result of custom mock behavior
val spyk = spyk<ServiceImplA>() //Mock dosomething 2, returns 111 no matter what input every { spyk.doSomething2(any()) } returns "111" val input = "222" //What I got here should be 111 val mockkResult = spyk.doSomething2(input) println("mockk Behavior results:$mockkResult") val real = ServiceImplA() //You should get 222 here val realResult = real.doSomething2(input) println("mockk Behavior results:$realResult")
Verify that multiple methods are called
If you want to verify that the two methods are executed, you can verify both methods in verify {...}. If the confirmation is successful, the test passes, otherwise an error is reported.
@Test fun test() { //Returns a mock object for ServiceImplA val mockk = mockk<ServiceImplA>() //mock specifies the method to set listening every { mockk.doSomething1(any()) } returns Unit every { mockk.doSomething2(any()) } returns Unit verify { mockk.doSomething1("sfas") mockk.doSomething2("sfas") } }
Assign a default behavior to a method that has no return value
By replacing the Returns after every {...} with just Runs, MockK can assign a default behavior to the method with no return value.
@Test fun test() { val serviceImplA = mockk<ServiceImplA>() every { serviceImplA.doSomething1(any()) } just Runs verify { serviceImplA.doSomething1(any()) } }
The number of times the validation method was called
If you want to verify not only that the method is called, but also the number of times the method is called, you can specify the exatcly, atLeast, and atMost properties in verify.
// Validation called twice verify(exactly = 2) { serviceImplA.doSomething1(any()) } // Validation was called at least once // verify(atLeast = 1) { serviceImplA.doSomething1(any()) } // Validation was called up to two times // verify(atMost = 2) { serviceImplA.doSomething1(any()) }
Verify that the Mock methods have been called
verifyAll { serviceImplA.doSomething1(any()) serviceImplA.doSomething2(any()) serviceImplA.doSomething3(any()) serviceImplA.doSomething4(any()) }
Verify that all Mock methods are called in a specific order
If you not only want to test that several methods have been called, but also want to ensure that they are called in a fixed order, you can use verifySequence {...}.
verifySequence { serviceImplA.doSomething1(any()) serviceImplA.doSomething2(any()) serviceImplA.doSomething3(any()) serviceImplA.doSomething4(any()) }
Validate mock object private method
Verification is placed in verify {...} and verified by reflection:
verify{ mockClass["privateFunName"](arg1, arg2, ...) }
It is mainly divided into three parts:
1.mock class
2. Square brackets, filled with double quotation marks + private method name
3. In parentheses, fill in the transmitted parameters
Note: the object class of mock also needs to set recordPrivateCalls to true
Delayed verification
Delay verification can be realized by using verify(timeout) {...}. For example, timeout = 2000 in the following code means to check whether the method is called after 2 seconds.
verify(timeout = 2000) { serviceImplA.doSomething1(any()) }
assertEquals
Judge whether the effectedNum is the same as the expected value 1. If it is different, test fail
assertEquals(expected:1,effectedNum)
relaxed
There is a Car under test, which depends on an Engine:
class Car(private val engine: Engine) { fun getSpeed(): Int { return engine.getSpeed() } } class Engine { fun getSpeed(): Int { return calSpeed() } private fun calSpeed(): Int { return 30 } }
To test getSpeed(), it depends on the methods in the Engine, so you need to mockk the Engine and write the following test methods:
fun testCar() { // mock engine object val engine = mockk<Engine>() val car = Car(engine) // Here is the writing method of private method setting listening: every { engine["calSpeed"]() } returns 30 val speed = car.getSpeed() assertEquals(speed, 30) }
However, an error is reported here: io.mockk.MockKException: no answer found for: Engine(#1).getSpeed()
This is because mockk strictly executes each method, and although Engine mocks it, mockk does not know whether Engine.getSpeed() needs to be executed further, so it throws an error.
At this time, you have three solutions.
Scheme 1: change the construction of engine from mock to spy, because spy can truly simulate the behavior of objects: engine = spyk()
Scheme 2: abandon the calSpeed method and use every {engine. Getspeed()} returns 30
Scheme 3: when mocking engine, set relaxed to true, engine = mockk(relaxed = true)