CodeMobile UK 2018
+++?image=assets/Slides_WW_EN.png&size=cover
Previously, in CodeMobileUk 2017 …
+++?image=assets/previously_2.png&size=contain
+++?image=assets/previously_1.png&size=contain
+++
You must be as confident in the test you code as you are in the code you test.
- Unit tests ?
- Mocks
- Mockito / KotlinMockito?
- Easymock ?
- JMockit ?
- Mockk ?
+++
Dumb POJO / Data Class, actual value is not relevant
@Test(expected = IndexOutOfBoundsException::class)
fun testThrowsException() {
val dummyUser = User(name = "Bob", id = 42)
testedList.put(-666, dummy)
}
+++
Objects with scripted answers
@Test(expected = IllegalArgumentException::class)
fun testReturnsNull() {
whenever(mockProvider.getData()) doReturn null
testedObject.addDataFrom(mockProvider)
}
+++
Can be mock or real objects, just tracking each call
@Test fun testCallsListener() {
val spiedListener = spy(realListener)
testedObject.addListener(spiedListener)
testedObject.doSomething()
verify(spiedListener).somethingDone()
}
+++
+++
- Where does the `42` come from ?
- Lots of hardcoded values !
- Only one value is tested. Ever.
+++
+++
@Test fun invalidateDataAfterTimeout() {
testedObject.setLastCallTimestamp(0)
testedObject.setDataTTL(42)
long ts = 666
boolean result = testedObject.isDataValid(ts)
assertFalse(result)
}
+++
@Test fun invalidateDataAfterTimeout() {
val fakeTS = rand.nextLong()
val fakeTTL = rand.nextLong()
val fakeDelay = rand.nextLong()
testedObject.setLastCallTimestamp(fakeTS)
testedObject.setDataTTL(fakeTTL)
val ts = fakeTS + fakeTTL + fakeDelay
val result = testedObject.isDataValid(ts)
assertFalse(result)
}
+++
+++
Introducing Elmyr
class FooTest {
@JvmField @Rule val forger = new JUnitForger()
// …
}
+++
// works with ints longs, floats and doubles
forger.anInt(min = 0, max =100)
forger.aPositiveLong()
forger.aGaussianFloat(mean = 42.0f, standardDeviation = 100.0f)
forger.aDoubleArray(DoubleConstraint.NEGATIVE_STRICT)
+++
forger.anHexadecimalString()
forger.aWord()
forger.aSentence()
forger.anEmail()
forger.aUrl()
forger.aStringArray(StringConstraint.WORD)
forger.aStringMatching("(0|+44)\\d{10}")
+++
forger.anElementFrom(myCollection)
forger.anElementFrom(value1, value2, value3, …)
forger.aValueFrom(MyEnum::class)
forger.aNullableFrom(nonNullValue)
+++
‘testSomething’ failed with fake seed = 0x4815162342
@Before fun forceSeed() {
forger.reset(4815162342L)
}
+++
@Test fun invalidateDataAfterTimeout(){
val fakeTS = forger.aTimestamp()
val fakeTTL = forger.aLong(1, 86400000)
val fakeDelay = forger.aLong(1, 86400000)
testedObject.setLastCallTimestamp(fakeTS)
testedObject.setDataTTL(fakeTTL)
val ts = fakeTS + fakeTTL + fakeDelay
val result = testedObject.isDataValid(ts)
assertFalse(result)
}
+++
“There are two types of mocks: inputs and outputs”
+++
@Test fun testSomething() {
val fakeData = forger.anInt()
val inputMock = mock()
whenever (inputMock.getData()) doReturn fakeData
// Call to getData is not verified directly
}
+++
@Test fun testSomething() {
val outputMock = mock()
// mock is not stubbed
verify(outputMock).onSomethingDone()
}
+++
+++
+++
@Test fun testFoo() {
whenever(mockQueue.isEmpty()) doReturn(true)
whenever(mockQueue.getFirst() doThrow(new Exception())
// …
}
@Test fun testBar() {
whenever(mockQueue.isEmpty()) doReturn(true)
whenever(mockQueue.getFirst() doReturn(null)
// …
}
+++
- Mocks have single configuration
- All stubbing is done in one place
+++
+++
class FooTest {
@Before fun setupMock() {
whenever(mockQueue.isEmpty()) doReturn(true)
whenever(mockQueue.getFirst() doThrow(new Exception())
}
}
class FizTest {
@Before fun setupMock() {
whenever(mockQueue.isEmpty()) doReturn(true)
whenever(mockQueue.getFirst() doReturn(null)
// …
}
}
+++
+++
class QueueContract {
val mockedQueue : Queue = mock()
fun prepareEmpty() {
whenever(mockedQueue.isEmpty()).thenReturn(true)
whenever(mockedQueue.getFirst().thenThrow(new Exception())
}
}
+++
class FooTest {
lateinit var queueContract : QueueContract
@Before fun setUpQueue() {
queueContract = new QueueContract()
}
@Test fun testWithEmptyQueue() {
queueContract.prepareEmpty()
val mock = queueContract.mockedQueue
// …
}
}
+++
- Fuzzy specs
- 3rd party implementation
- Undocumented behavior
+++
+++
Introducing Mesmaeker
(Still in αlphα)
+++
open class QueueContract
: MockitoContract<Queue>(Queue::class.java) {
@Clause
fun prepareEmpty() {
whenever { it.isEmpty() }.thenReturn(true)
whenever { it.getFirst() }.thenThrow(new Exception())
}
}
+++
class FooTest {
@Rule public BaseContractRule contracts = new BaseContractRule();
@Contract lateinit var queueContract: QueueContract
@Test fun testWithEmptyQueue() {
queueContract.prepareEmpty()
val mock = queueContract.getMock()
// …
}
}
+++
class BarTest (clause : String)
: ContractValidator<Queue, QueueContract> (clause) {
override fun instantiateContract()
: QueueContract = QueueContract()
override fun instantiateSubject()
: Queue = Queue()
companion object {
@JvmStatic @Parameterized.Parameters()
fun data(): Collection<Array<Any?>> {
return generateTestParameters(QueueContract::class.java)
}
}
}
+++
@Clause
fun prepareWithSize(size: Int) {
applyIfImplementation { it.resize(size) }
whenever { it.size() }.thenReturn(size)
whenever { it.get(-1) }.thenThrow(IooBException())
whenever { it.get(size) }.thenThrow(IooBException())
}
+++
override fun getClauseParams(clause: String): Array<Any?>? {
when (contractClause) {
"prepareWithSize" -> return arrayOf(forger.aSmallInt())
else -> return emptyArray()
}
}
- Remember, tests can have bugs and code smells too
- Try to think of what could go wrong
- Look for the edge cases
+++
“You need to be as confident in the code you test as you are in the test you code ”