1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.testutils 18 19 import org.mockito.Answers 20 import org.mockito.ArgumentMatchers 21 import org.mockito.Mockito 22 import org.mockito.invocation.InvocationOnMock 23 import org.mockito.stubbing.Answer 24 import org.mockito.stubbing.Stubber 25 26 object MockitoUtils { 27 val ANSWER_THROWS = Answer<Any?> { 28 when (val name = it.method.name) { 29 "toString" -> return@Answer try { 30 Answers.CALLS_REAL_METHODS.answer(it) 31 } catch (e: Exception) { 32 "failure calling toString" 33 } 34 else -> { 35 val arguments = it.arguments 36 ?.takeUnless { it.isEmpty() } 37 ?.mapIndexed { index, arg -> arg.attemptToString(index) } 38 ?.joinToString { it.attemptToString(null) } 39 ?.let { "with $it" } 40 .orEmpty() 41 42 throw UnsupportedOperationException("${it.mock::class.java.simpleName}#$name " + 43 "$arguments should not be called") 44 } 45 } 46 } 47 48 // Sometimes mocks won't have a toString method, so try-catch and return some default 49 private fun Any?.attemptToString(id: Any? = null): String { 50 return try { 51 toString() 52 } catch (e: Exception) { 53 if (id == null) { 54 e.message ?: "ERROR" 55 } else { 56 "$id ${e.message}" 57 } 58 } 59 } 60 } 61 62 inline fun <reified T> mock(block: T.() -> Unit = {}) = Mockito.mock(T::class.java).apply(block) 63 64 fun <T> spy(value: T, block: T.() -> Unit = {}) = Mockito.spy(value).apply(block) 65 66 fun <Type> Stubber.whenever(mock: Type) = this.`when`(mock) 67 fun <Type : Any?> whenever(mock: Type) = Mockito.`when`(mock) 68 69 @Suppress("UNCHECKED_CAST") 70 fun <Type> whenever(mock: Type, block: InvocationOnMock.() -> Type) = 71 Mockito.`when`(mock).thenAnswer { block(it) } 72 73 fun whenever(mock: Unit) = Mockito.`when`(mock).thenAnswer { } 74 75 inline fun <reified T> spyThrowOnUnmocked(value: T?, block: T.() -> Unit = {}): T { 76 val swappingAnswer = object : Answer<Any?> { 77 var delegate: Answer<*> = Answers.RETURNS_DEFAULTS 78 79 override fun answer(invocation: InvocationOnMock?): Any? { 80 return delegate.answer(invocation) 81 } 82 } 83 84 return Mockito.mock(T::class.java, Mockito.withSettings().spiedInstance(value) 85 .defaultAnswer(swappingAnswer)).apply(block) 86 .also { 87 // To allow when() usage inside block, only swap to throwing afterwards 88 swappingAnswer.delegate = MockitoUtils.ANSWER_THROWS 89 } 90 } 91 92 inline fun <reified T> mockThrowOnUnmocked(block: T.() -> Unit = {}) = 93 spyThrowOnUnmocked<T>(null, block) 94 95 inline fun <reified T : Any> nullable() = ArgumentMatchers.nullable(T::class.java) 96 97 /** 98 * Returns Mockito.any() as nullable type to avoid java.lang.IllegalStateException when 99 * null is returned. 100 * 101 * Generic T is nullable because implicitly bounded by Any?. 102 */ 103 fun <T> any(type: Class<T>): T = Mockito.any<T>(type) 104 105 /** 106 * Wrapper around [Mockito.any] for generic types. 107 */ 108 inline fun <reified T> any() = any(T::class.java) 109 110 /** 111 * Returns Mockito.eq() as nullable type to avoid java.lang.IllegalStateException when 112 * null is returned. 113 * 114 * Generic T is nullable because implicitly bounded by Any?. 115 */ 116 fun <T> eq(obj: T): T = Mockito.eq<T>(obj) 117