Skip to content

Commit

Permalink
Merge branch 'kt12' into kotlin13
Browse files Browse the repository at this point in the history
# Conflicts:
#	gradle.properties
  • Loading branch information
oleksiyp committed Mar 10, 2019
2 parents effaf7e + 818cff0 commit 789e944
Show file tree
Hide file tree
Showing 31 changed files with 484 additions and 1,228 deletions.
5 changes: 5 additions & 0 deletions .github/issue_template.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
> Just be aware about usual MockK support pattern.
> Tickets are checked from time to time, replied, discussed, labeled, e.t.c.
> But real fixes are applied in a month-two month period in a bunch.
> If you think this is unacceptable, go on, join the project, change the world.
## Please remove sections wisely

Below information is actually needed to make all the process of fixing faster.
Expand Down
6 changes: 5 additions & 1 deletion ANDROID.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ MockK supports:
* Android instrumented tests via subclassing(< Android P)
* Android instrumented tests via inlining(≥ Android P)

## DexOpener

To open classes before Android P you can use [DexOpener](https://github.com/tmurakami/dexopener), [example](https://github.com/tmurakami/dexopener/tree/master/examples/mockk)

## Implementation

Implementation is based on [dexmaker](https://github.com/linkedin/dexmaker) project. With Anroid P instrumentation tests may use full power of inline instrumentation, so object mocks, static mocks and mocking of final classes are supported. Before Android P only subclassing can be employed and that means you need 'all-open' plugin.
Expand Down Expand Up @@ -44,7 +48,7 @@ Unfortunatelly public CIs alike Travis and Circle are not supporting emulation o
<tr>
<td>mocking final classes</td>
<td>✓</td>
<td></td>
<td>can use DexOpener</td>
<td>✓</td>
</tr>
<tr>
Expand Down
142 changes: 104 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,26 @@
![mockk](doc/logo-site.png) ![kotlin](doc/kotlin-logo.png)

[![Gitter](https://badges.gitter.im/mockk-io/Lobby.svg)](https://gitter.im/mockk-io/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=body_badge)
[![Build Status](https://travis-ci.org/mockk/mockk.svg?branch=master)](https://travis-ci.org/mockk/mockk)
[![Relase Version](https://img.shields.io/maven-central/v/io.mockk/mockk.svg?label=release)](http://search.maven.org/#search%7Cga%7C1%7Cmockk)
[![Change log](https://img.shields.io/badge/change%20log-%E2%96%A4-yellow.svg)](https://github.com/mockk/mockk/releases)
[![codecov](https://codecov.io/gh/mockk/mockk/branch/master/graph/badge.svg)](https://codecov.io/gh/mockk/mockk)
[![Weekly users](https://us-central1-bot-mockk.cloudfunctions.net/bot-mockk)](https://github.com/mockk/mockk)
[![Backers](https://opencollective.com/mockk/tiers/backer/badge.svg?label=backer&color=brightgreen)](https://opencollective.com/mockk)

[![Android](https://img.shields.io/badge/android-support-green.svg)](http://mockk.io/ANDROID)
[![Matrix tests](https://img.shields.io/badge/matrix-test-e53994.svg)](http://mockk.io/MATRIX)
[![Back log](https://img.shields.io/badge/back%20log-%E2%96%A4-orange.svg)](/BACKLOG)
[![Deprecated](https://img.shields.io/badge/deprecated-API-red.svg)](/DEPRECATED)
[![Documentation](https://img.shields.io/badge/documentation-%E2%86%93-yellowgreen.svg)](#nice-features)
[![GitHub stars](https://img.shields.io/github/stars/mockk/mockk.svg?label=stars)](https://github.com/mockk/mockk)
[![Become sponsor](https://opencollective.com/mockk/tiers/sponsor/badge.svg?label=become+sponsor&color=green)](https://opencollective.com/mockk)

<img src="doc/new.png" align="left" height="80" alt="new" />
### Kotlin Academy articles <img src="https://cdn-images-1.medium.com/letterbox/47/47/50/50/1*FUXqI88mttV_kV8aTrKjOg.png?source=logoAvatar-1f9f77b4b3d1---e57b304801ef" width="20px" />

Check the series of articles "Mocking is not rocket science" at [Kt. Academy](https://blog.kotlin-academy.com) describing MockK from the very basics of mocking up to description of all advanced features.

- [Basics](https://blog.kotlin-academy.com/mocking-is-not-rocket-science-basics-ae55d0aadf2b)
- [Expected behavior and behavior verification](https://blog.kotlin-academy.com/mocking-is-not-rocket-science-expected-behavior-and-behavior-verification-3862dd0e0f03)
- [MockK features](https://blog.kotlin-academy.com/mocking-is-not-rocket-science-mockk-features-e5d55d735a98)
- [MockK advanced features](https://blog.kotlin-academy.com/mocking-is-not-rocket-science-mockk-advanced-features-42277e5983b5)

* TDD for Android tutorial [part 1](https://www.youtube.com/watch?v=60KFJTb_HwU), [part 2](https://www.youtube.com/watch?v=32pnzGirvgM) by Ryan Kay
* New feature: [verification confirmation](#verification-confirmation) [#207](https://github.com/mockk/mockk/pull/207)
* MockK is now present on [Thoughtworks technology radar](https://www.thoughtworks.com/radar/languages-and-frameworks/mockk)
### Spring support

* [springmockk](https://github.com/Ninja-Squad/springmockk) introduced in official [Spring Boot Kotlin tutorial](https://spring.io/guides/tutorials/spring-boot-kotlin/)

### Version twist

Expand All @@ -39,24 +39,6 @@ Table of contents:
* auto-gen TOC:
{:toc}

## Nice features

- annotations
- mocking final classes and functions (via inlining)
- pure Kotlin mocking DSL
- matchers partial specification
- chained calls
- matcher expressions
- mocking coroutines
- capturing lambdas
- object mocks
- constructor mocks
- private function mocking
- property backing field access
- extension function mocking (static mocks)
- [Android instrumented tests](ANDROID)
- multiplatform support (JS support is highly experimental)

## Examples & articles
- TDD for Android tutorial [part 1](https://www.youtube.com/watch?v=60KFJTb_HwU), [part 2](https://www.youtube.com/watch?v=32pnzGirvgM) by Ryan Kay
- [https://github.com/PhilippeBoisney/NoBullshit](https://github.com/PhilippeBoisney/NoBullshit)
Expand All @@ -74,12 +56,6 @@ Table of contents:
- [Habrahabr article](https://habrahabr.ru/post/341202/) (RU)
- [Mocking in Kotlin with MockK - Yannick De Turck](https://ordina-jworks.github.io/testing/2018/02/05/Writing-tests-in-Kotlin-with-MockK.html)

#### Kotlin Academy <img src="https://cdn-images-1.medium.com/letterbox/47/47/50/50/1*FUXqI88mttV_kV8aTrKjOg.png?source=logoAvatar-1f9f77b4b3d1---e57b304801ef" width="20px" />

- [Mocking is not rocket science: Basics](https://blog.kotlin-academy.com/mocking-is-not-rocket-science-basics-ae55d0aadf2b)
- [Mocking is not rocket science: Expected behavior and behavior verification](https://blog.kotlin-academy.com/mocking-is-not-rocket-science-expected-behavior-and-behavior-verification-3862dd0e0f03)
- [Mocking is not rocket science: MockK features](https://blog.kotlin-academy.com/mocking-is-not-rocket-science-mockk-features-e5d55d735a98)

## Installation

All you need to get started is just to add a dependency to `MockK` library.
Expand Down Expand Up @@ -282,7 +258,7 @@ every { func() } returns Car() // or you can return mockk() for example
func()
```

### Unit returning function relaxed mock
### Mock relaxed for functions returning Unit

In case you would like `Unit` returning functions to be relaxed.
You can use `relaxUnitFun = true` as an argument to `mockk` function,
Expand Down Expand Up @@ -461,6 +437,45 @@ every { obj.op2(1, 2).hint(Int::class).op1(3, 4) } returns 5

```

### Hierarchical mocking

From version 1.9.1 mocks may be chained into hierarchies:

```kotlin
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
}

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"
}
}
)
}
```

### Capturing

Expand Down Expand Up @@ -798,6 +813,44 @@ For example `File.endsWith()` extension function has totally unpredictable `clas
This is standard Kotlin behaviour that may be unpredictable for user of mocking library.
Use `Tools -> Kotlin -> Show Kotlin Bytecode` or check `.class` files in JAR archive to detect such names.

### Varargs

From version 1.9.1 more extended vararg handling is possible:

```kotlin
interface ClsWithManyMany {
fun manyMany(vararg x: Any): Int
}

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
```

### Private functions mocking / dynamic calls

In case you have a need to mock private function, you can do it via dynamic call.
Expand Down Expand Up @@ -976,8 +1029,8 @@ By default simple arguments are matched using `eq()`
|`cmpEq(value)`|matches if value is equal to the provided via compareTo function|
|`less(value)`|matches if value is less to the provided via compareTo function|
|`more(value)`|matches if value is more to the provided via compareTo function|
|`less(value, andEquals=false)`|matches if value is less or equals to the provided via compareTo function|
|`more(value, andEquals=false)`|matches if value is more or equals to the provided via compareTo function|
|`less(value, andEquals=true)`|matches if value is less or equals to the provided via compareTo function|
|`more(value, andEquals=true)`|matches if value is more or equals to the provided via compareTo function|
|`range(from, to, fromInclusive=true, toInclusive=true)`|matches if value is in range via compareTo function|
|`and(left, right)`|combines two matchers via logical and|
|`or(left, right)`|combines two matchers via logical or|
Expand All @@ -990,6 +1043,12 @@ By default simple arguments are matched using `eq()`
|`invoke(...)`|calls matched argument|
|`coInvoke(...)`|calls matched argument for coroutine|
|`hint(cls)`|hints next return type in case it's got erased|
|`anyVararg()`|matches any elements in vararg|
|`varargAny(matcher)`|matches if any element is matching matcher|
|`varargAll(matcher)`|matches if all elements are matching matcher|
|`any...Vararg()`|matches any elements in vararg(specific to primitive type)|
|`varargAny...(matcher)`|matches if any element is matching matcher(specific to primitive type)|
|`varargAll...(matcher)`|matches if all elements are matching matcher(specific to primitive type)|

Few special matchers available in verification mode only:

Expand Down Expand Up @@ -1073,6 +1132,13 @@ So this has similiar to `returnsMany` semantics.
|`value`|value being set casted to same type as property backing field|
|`valueAny`|value being set with `Any?` type|

### Vararg scope

|Parameter|Description|
|---------|-----------|
|`position`|a position of argument in vararg array|
|`nArgs`|overall count of arguments in vararg array|

## Funding

[![Become baker](https://opencollective.com/mockk/tiers/backer.svg?avatarHeight=150)](https://opencollective.com/mockk)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,14 @@ internal class AndroidInlineInstrumentation(
private val agent: JvmtiAgent
) : RetransformInlineInstrumnetation(log, specMap) {

override fun retransform(classes : Array<Class<*>>) {
agent.requestTransformClasses(classes.filter { !it.isInterface }.toTypedArray())
override fun retransform(classesToTransform: Collection<Class<*>>) {
val classes = classesToTransform.filter { !it.isInterface }.toTypedArray()

if (!classes.isEmpty()) {
log.trace("Retransforming $classes (skipping interfaces)")
agent.requestTransformClasses(classes)
} else {
log.trace("No need to transform")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,24 @@ package io.mockk.proxy.common.transformation

import io.mockk.proxy.common.transformation.TransformationType.*
import java.util.*
import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock

class ClassTransformationSpecMap {
private val classSpecs = WeakHashMap<Class<*>, ClassTransformationSpec>()
private val transformationLock = ReentrantLock(true)
private val specLock = ReentrantLock()

fun applyTransformationRequest(request: TransformationRequest) =
synchronized(classSpecs) {
val result = mutableListOf<Class<*>>()
fun applyTransformation(
request: TransformationRequest,
retransformClasses: (TransformationRequest) -> Unit
) = transformationLock.withLock {
val result = mutableListOf<Class<*>>()

specLock.withLock {
for (cls in request.classes) {
val spec = classSpecs[cls]
?: ClassTransformationSpec(cls)
?: ClassTransformationSpec(cls)

val diff = if (request.untransform) -1 else 1

Expand All @@ -29,17 +36,18 @@ class ClassTransformationSpecMap {
result.add(cls)
}
}

request.copy(classes = result.toSet())
}

retransformClasses(request.copy(classes = result.toSet()))
}

fun shouldTransform(clazz: Class<*>?) =
synchronized(classSpecs) {
specLock.withLock {
classSpecs[clazz] != null
}

operator fun get(clazz: Class<*>?) =
synchronized(classSpecs) {
specLock.withLock {
classSpecs[clazz]
?.apply {
if (!shouldDoSomething) {
Expand All @@ -49,7 +57,7 @@ class ClassTransformationSpecMap {
}

fun transformationMap(request: TransformationRequest): Map<String, String> =
synchronized(classSpecs) {
specLock.withLock {
request.classes.map { it.simpleName to classSpecs[it].toString() }.toMap()
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,50 +1,45 @@
package io.mockk.proxy.common.transformation

import io.mockk.proxy.MockKAgentLogger
import java.lang.instrument.UnmodifiableClassException

abstract class RetransformInlineInstrumnetation(
private val log: MockKAgentLogger,
protected val log: MockKAgentLogger,
private val specMap: ClassTransformationSpecMap
) : InlineInstrumentation {

protected abstract fun retransform(classes: Array<Class<*>>)
protected abstract fun retransform(classesToTransform: Collection<Class<*>>)

override fun execute(request: TransformationRequest): () -> Unit {
val instrumentationRequest = specMap.applyTransformationRequest(
request
)

val cancellation = { doCancel(request) }

var cancellation: (() -> Unit)? = null
try {
val classes = instrumentationRequest.classes.toTypedArray()
if (classes.isNotEmpty()) {
log.trace("Retransforming ${specMap.transformationMap(instrumentationRequest)}")
retransform(classes)
specMap.applyTransformation(request) {
cancellation = { doCancel(request) }

retransform(it.classes)
}
} catch (ex: Exception) {
log.warn(ex, "Failed to transform classes $instrumentationRequest")
cancellation()
} catch (ex: java.lang.Exception) {
log.warn(ex, "Failed to transform classes ${request.classes}")
cancellation?.invoke()
return {}
}

return cancellation
return cancellation ?: {}
}

private fun doCancel(request: TransformationRequest) {
val reverseInstrumentationRequest =
specMap.applyTransformationRequest(
request.reverse()
)

try {
val classes = reverseInstrumentationRequest.classes.toTypedArray()
if (classes.isNotEmpty()) {
log.trace("Retransforming back ${specMap.transformationMap(reverseInstrumentationRequest)}")
retransform(classes)
specMap.applyTransformation(
request.reverse()
) {
val classes = it.classes
if (classes.isNotEmpty()) {
log.trace("Retransforming back ${it.classes}")
retransform(classes)
}
}
} catch (ex: Exception) {
log.warn(ex, "Failed to revert class transformation ${reverseInstrumentationRequest.classes}")
} catch (ex: UnmodifiableClassException) {
log.warn(ex, "Failed to revert class transformation ${request.classes}")
}
}
}

0 comments on commit 789e944

Please sign in to comment.