What’s the problem, again?

Most stubs come with a default behavior. In Java land, Mockito stubs return null, false, etc. when no specific behavior has been rehearsed for the call. In JavaScript land, both testdouble.js stubs and Sinon stubs return undefined, if you call them in unrehearsed fashion.

import td from 'testdouble'

const stub = td.func()

td.when(stub(0)).thenReturn('foo')

assertThat(stub(0), is('foo'))

assertThat(stub(), is(undefined))

This can be even be handy sometimes. Often, however, if you do something with the return value, either process it or pass it on, undefined is likely to cause you problems.

In the following example, the function under test (fut) calls the stub without any params and then calls substring(1) on the return value of the stub. We set up the assertion assertThat(fut(stub), is(‘oo’)) assuming the the stub will return foo. It won’t, however, because, for whatever reason, we rehearsed its behavior differently. So it will return undefined and we end up with an error message that is more confusing than anything else, as it does not point to the root cause of the problem:

TypeError: Cannot read property ‘substring’ of undefined

In executable terms:

describe('Problem', () => {

it('unrehearsed usage fails late because of the consequences of default stub behavior', () => {

const stub = td.function()

td.when(stub(0)).thenReturn('foo') const fut = (collaborator) => collaborator().substring(1) assertThat(

() => assertThat(fut(stub), is('oo')),

throws(

typedError(TypeError, "Cannot read property 'substring' of undefined")

)

)

})

})

The error you observe is not a consequence of your code under test misbehaving. Rather, the error is a consequence of something entirely different. — Imagine you present with a hot forehead. Did you just run 10k or are you feverish because you have a flu?

Wouldn’t it be great, if the stub told us that it was being used in unexpected fashion?

Enter testdouble-only-when. Let’s rehearse the stub’s behavior using onlyWhen instead of td.when.

import { onlyWhen } from 'testdouble-only-when'

const stub = td.function()

onlyWhen(stub(0)).thenReturn('foo')

Now, using the stub as rehearsed behaves as previously:

assertThat(stub(0), is('foo'))

However, calling the stub differently, e.g. stub() no longer returns undefined. It now results in:

Error: You invoked a test double in an unexpected fashion.

This test double has 1 stubbings and 1 invocations. Stubbings:

— when called with `(0)`, then return `”foo”`. Invocations:

— called with `()`.

at …

The test fails early and you know more precisely what to look for.