Skip to content

Commit

Permalink
Handle null in argument matchers.
Browse files Browse the repository at this point in the history
When stubbing with argument matchers, the following
scenario would cause a NullPointerException:

```
whenever(mock.doSomething(argThat { })).thenReturn("")
whenever(mock.doSomething(argThat { })).thenReturn("")
```

The second `argThat` invocation returns a `null` value,
which gets propagated into the function passed to the
first `argThat` invocation.

This commit filters `null` values to always evaluate to
`false` when provided to an argument matcher.
  • Loading branch information
nhaarman committed Dec 8, 2016
1 parent 543238f commit b77d969
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,39 @@ inline fun <reified T : Any> anyVararg(): T = Mockito.any<T>() ?: createInstance
/** Matches any array of type T. */
inline fun <reified T : Any?> anyArray(): Array<T> = Mockito.any(Array<T>::class.java) ?: arrayOf()

inline fun <reified T : Any> argThat(noinline predicate: T.() -> Boolean) = Mockito.argThat<T> { it -> (it as T).predicate() } ?: createInstance(T::class)
/**
* Creates a custom argument matcher.
* `null` values will never evaluate to `true`.
*
* @param predicate An extension function on [T] that returns `true` when a [T] matches the predicate.
*/
inline fun <reified T : Any> argThat(noinline predicate: T.() -> Boolean) = Mockito.argThat<T?> { arg -> arg?.predicate() ?: false } ?: createInstance(T::class)

/**
* Creates a custom argument matcher.
* `null` values will never evaluate to `true`.
*
* @param predicate An extension function on [T] that returns `true` when a [T] matches the predicate.
*/
inline fun <reified T : Any> argForWhich(noinline predicate: T.() -> Boolean) = argThat(predicate)
inline fun <reified T : Any> check(noinline predicate: (T) -> Unit) = Mockito.argThat<T> { it -> predicate(it); true } ?: createInstance(T::class)

/**
* For usage with verification only.
*
* For example:
* verify(myObject).doSomething(check { assertThat(it, is("Test")) })
*
* @param predicate A function that performs actions to verify an argument [T].
*/
inline fun <reified T : Any> check(noinline predicate: (T) -> Unit) = Mockito.argThat<T?> { arg ->
if (arg == null) error("""The argument passed to the predicate was null.
If you are trying to verify an argument to be null, use `isNull()`.
If you are using `check` as part of a stubbing, use `argThat` or `argForWhich` instead.
""".trimIndent())
predicate(arg)
true
} ?: createInstance(T::class)

fun atLeast(numInvocations: Int): VerificationMode = Mockito.atLeast(numInvocations)!!
fun atLeastOnce(): VerificationMode = Mockito.atLeastOnce()!!
Expand Down
5 changes: 4 additions & 1 deletion mockito-kotlin/src/test/kotlin/test/Classes.kt
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
package test/*
package test

/*
* The MIT License
*
* Copyright (c) 2016 Niek Haarman
Expand Down Expand Up @@ -55,6 +57,7 @@ interface Methods {
fun nullableString(s: String?)

fun stringResult(): String
fun stringResult(s: String): String
fun nullableStringResult(): String?
fun builderMethod(): Methods
}
Expand Down
36 changes: 36 additions & 0 deletions mockito-kotlin/src/test/kotlin/test/MockitoTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,17 @@ class MockitoTest : TestBase() {
}
}

@Test
fun checkWithNullArgument_throwsError() {
mock<Methods>().apply {
nullableString(null)

expectErrorWithMessage("null").on {
verify(this).nullableString(check {})
}
}
}

@Test
fun atLeastXInvocations() {
mock<Methods>().apply {
Expand Down Expand Up @@ -458,6 +469,30 @@ class MockitoTest : TestBase() {
}
}

@Test
fun stubbingTwiceWithArgumentMatchers() {
/* When */
val mock = mock<Methods> {
on { stringResult(argThat { this == "A" }) } doReturn "A"
on { stringResult(argThat { this == "B" }) } doReturn "B"
}

/* Then */
expect(mock.stringResult("A")).toBe("A")
expect(mock.stringResult("B")).toBe("B")
}

@Test
fun stubbingTwiceWithCheckArgumentMatchers_throwsException() {
/* Expect */
expectErrorWithMessage("null").on {
mock<Methods> {
on { stringResult(check { }) } doReturn "A"
on { stringResult(check { }) } doReturn "B"
}
}
}

@Test
fun doReturn_withSingleItemList() {
/* Given */
Expand Down Expand Up @@ -500,6 +535,7 @@ class MockitoTest : TestBase() {
verify(this).string(isA<String>())
}
}

@Test
fun isA_withNullableString() {
mock<Methods>().apply {
Expand Down

0 comments on commit b77d969

Please sign in to comment.