Skip to content

Joyful mocking library for Typescript and Javascript

License

Notifications You must be signed in to change notification settings

nbottarini/mockt

Repository files navigation

Mockt

 

Joyful mocking library for Typescript and Javascript

npm License: MIT CI Status

Getting Started

Npm:

$ npm install --save-dev mockt

Yarn:

$ yarn add -D mockt

Now let's write a simple test:

const calculator = mockt(Calculator)
when(calculator).sum(2, any()).returns(3)

const actual = calculator.sum(2, 8)

expect(actual).toEqual(3)
verify(calculator).sum(any(), 8)

Features

  • Typescript Support and IDE Autocompletion
  • Class mocks
  • Abstract class mocks
  • Interface mocks
  • Generics support
  • Flexible stubbing with multiple returns, custom function call, errors, promises and stub overriding.
  • Properties stubbing
  • Spy on real objects
  • Argument Capturing
  • Verification atLeast, atMost, once, times(n)
  • Verification called and never called
  • Properties read and write verification
  • Verification order
  • Argument Matchers for stubs and verifications
  • Reset mock stubs
  • Reset mock recorded calls

Table of Contents

Usage

Class mocks

class Calculator {
    sum(a: number, b: number): number {
        return a + b
    }
}

const calculator = mockt(Calculator)
when(calculator).sum(2, 3).returns(5)

calculator.sum(2, 3) // returns 5

Arrow methods

class Calculator {
    sum = (a: number, b: number) => a + b
}

const calculator = mockt(Calculator)
when(calculator).sum(2, 3).returns(5)

calculator.sum(2, 3) // returns 5

Note: arrow methods fails when called without stubbing them.

Abstract Class mocks

abstract class Calculator {
    abstract sum(a: number, b: number): number
}

const calculator = mockt(Calculator)
when(calculator).sum(2, 3).returns(5)

calculator.sum(2, 3) // returns 5

Note: abstract methods fails when called without stubbing them.

Interface mocks

interface Calculator {
    sum(a: number, b: number): number
}

const calculator = mockt<Calculator>()
when(calculator).sum(2, 3).returns(5)

calculator.sum(2, 3) // returns 5

Note: Interfaces are passed as Generic Parameter to mockt function.

Note: Interface methods fails when called without stubbing them.

Generics

interface List<T> {
    add(item: T): void
    get(index: number): T
}

const list = mockt(List<string>)
when(list).get(3).returns('Item 3')

list.get(3) // returns 'Item 3'

Properties

class User {
    public name: string
}

const user = mockt(User)
when(user).name.returns('Alice')

user.name // returns 'Alice'

Getters

class User {
    private _name: string
    
    get name(): string {
        return this._name
    }
}

const user = mockt(User)
when(user).name.returns('Alice')

user.name // returns 'Alice'

Stubbing

Multiple returns

const calculator = mockt(Calculator)
when(calculator).sum(2, 3).returns(5, 6, 7)

calculator.sum(2, 3) // returns 5
calculator.sum(2, 3) // returns 6
calculator.sum(2, 3) // returns 7
calculator.sum(2, 3) // returns 7

Stub overriding

const calculator = mockt(Calculator)
when(calculator).sum(2, 3).returns(5)
when(calculator).sum(2, 3).returns(6)

calculator.sum(2, 3) // returns 6
calculator.sum(2, 3) // returns 6
const calculator = mockt(Calculator)
when(calculator).sum(any(), any()).returns(5, 6)
when(calculator).sum(2, 3).returns(7)

calculator.sum(2, 3) // returns 7
calculator.sum(2, 4) // returns 5
calculator.sum(2, 5) // returns 6

Function call

const calculator = mockt(Calculator)
when(calculator).sum(any(), any()).calls((a, b) => a * b)

calculator.sum(2, 3) // returns 6
calculator.sum(3, 4) // returns 12

Throw error

const calculator = mockt(Calculator)
when(calculator).sum(any(), any()).throws(new Error('Some error'))

calculator.sum(2, 3) // throws Error('Some error')

Promise resolve

const apiClient = mockt(ApiClient)
when(apiClient).getUser().resolves(user1, user2)

await apiClient.getUser() // returns user 1
await apiClient.getUser() // returns user 2

Promise reject

const apiClient = mockt(ApiClient)
when(apiClient).getUser().rejects(new Error('Some error'))

await apiClient.getUser() // throws Error('Some error')

Chained answers

const calculator = mockt(Calculator)
when(calculator).sum(2, 3)
        .returns(5)
        .returns(6)
        .calls(() => 7)
        .throws(new Error('Some error'))

calculator.sum(2, 3) // returns 5
calculator.sum(2, 3) // returns 6
calculator.sum(2, 3) // returns 7
calculator.sum(2, 3) // throws Error('Some error')

Matchers

Equals

const calculator = mockt(Calculator)
when(calculator).sum(2, eq(3)).returns(5)

calculator.sum(2, 3) // returns 5
calculator.sum(2, 4) // returns undefined

Note: When a value is given instead of a matcher, the equals matcher is used. Note: Performs deep equality

Deep equality and type conversion
const billingService = mockt(BillingService)
when(billingService).calculateFor(eq({ user: { age: '17' } })).returns(100)

billingService.calculateFor({ user: { age: 17 } }) // returns 100

Not Equals

const calculator = mockt(Calculator)
when(calculator).sum(2, neq(3)).returns(5)

calculator.sum(2, 6) // returns 5
calculator.sum(2, 3) // returns undefined

Identical

const billingService = mockt(BillingService)
const alice = { name: 'Alice' }
when(billingService).calculateFor(is(alice)).returns(100)

billingService.calculateFor(alice) // returns 100
billingService.calculateFor({ name: 'Alice' }) // returns undefined

Any

const calculator = mockt(Calculator)
when(calculator).sum(2, any()).returns(5)

calculator.sum(2, 5) // returns 5
calculator.sum(2, 6) // returns 5
calculator.sum(3, 5) // returns undefined

Any Number

const calculator = mockt(Calculator)
when(calculator).sum(anyNumber(), any()).returns(5)

calculator.sum(1, 2) // returns 10
calculator.sum('1', 2) // returns undefined
calculator.sum(1, '2') // returns 10

Any String

const splitter = mockt(StringSplitter)
when(splitter).split(anyString()).returns(['Hello', 'World'])

splitter.split('Hello World') // returns ['Hello', 'World']
splitter.split('Bye') // returns ['Hello', 'World']
splitter.split(3) // returns undefined

Any Array

const calculator = mockt(Calculator)
when(calculator).average(anyArray()).returns(10)

calculator.average([1, 2]) // returns 10
calculator.average([3, 4]) // returns 10
calculator.average([]) // returns 10
calculator.average(3) // returns undefined

Any Object

const billingService = mockt(BillingService)
when(billingService).calculateFor(anyObject()).returns(2000)

billingService.calculateFor({ name: 'Alice', lastname: 'Jones' }) // returns 2000
billingService.calculateFor({}) // returns 2000
billingService.calculateFor('alice') // returns undefined

Any Function

const caller = mockt(FunctionCalled)
when(caller).call(anyFunction()).returns(10)

caller.call(() => {}) // returns 10
caller.call(function () {}) // returns 10
caller.call('alice') // returns undefined

IsNull

const billingService = mockt(BillingService)
when(billingService).calculateFor(isNull()).throws(new Error('User cannot be null'))

billingService.calculateFor(null) // throws Error('User cannot be null')
billingService.calculateFor({ name: 'Alice' }) // Doesn't throw
billingService.calculateFor(undefined) // Doesn't throw
billingService.calculateFor('') // Doesn't throw
billingService.calculateFor([]) // Doesn't throw

IsNil

const billingService = mockt(BillingService)
when(billingService).calculateFor(isNil()).throws(new Error('User cannot be null'))

billingService.calculateFor(null) // throws Error('User cannot be null')
billingService.calculateFor(undefined) // throws Error('User cannot be null')
billingService.calculateFor({ name: 'Alice' }) // Doesn't throw
billingService.calculateFor('') // Doesn't throw
billingService.calculateFor([]) // Doesn't throw

NotNull

const billingService = mockt(BillingService)
when(billingService).calculateFor(notNull()).returns(5000)

billingService.calculateFor({ name: 'Alice' }) // returns 5000
billingService.calculateFor(undefined) // returns 5000
billingService.calculateFor(null) // returns undefined

NotNil

const billingService = mockt(BillingService)
when(billingService).calculateFor(notNil()).returns(5000)

billingService.calculateFor({ name: 'Alice' }) // returns 5000
billingService.calculateFor(undefined) // returns undefined
billingService.calculateFor(null) // returns undefined

Less

const calculator = mockt(Calculator)
when(calculator).sum(less(5), any()).returns(10)

calculator.sum(4, 2) // returns 10
calculator.sum(5, 2) // returns 10
calculator.sum(6, 2) // returns undefined

More

const calculator = mockt(Calculator)
when(calculator).sum(more(5), any()).returns(10)

calculator.sum(6, 2) // returns 10
calculator.sum(5, 2) // returns 10
calculator.sum(4, 2) // returns undefined

Range

const calculator = mockt(Calculator)
when(calculator).sum(range(5, 10), any()).returns(10)

calculator.sum(4, 2) // returns undefined
calculator.sum(5, 2) // returns 10
calculator.sum(6, 2) // returns 10
calculator.sum(9, 2) // returns 10
calculator.sum(10, 2) // returns 10
calculator.sum(11, 2) // returns undefined

OfClass

const billingService = mockt(BillingService)
when(billingService).calculateFor(ofClass(Employee)).returns(2000)

billingService.calculateFor(new Employee('Alice')) // returns 2000
billingService.calculateFor(new User('Alice')) // returns undefined

Nested matchers

const billingService = mockt(BillingService)
when(billingService).calculateFor(eq({ name: 'Alice', lastname: any() })).returns(2000)

billingService.calculateFor({ name: 'Alice', lastname: 'Jones' }) // returns 2000
const calculator = mockt(Calculator)
when(calculator).average(eq([1, any(), 3])).returns(1)

calculator.average([1, 2, 3]) // returns 1

Not

const calculator = mockt(Calculator)
when(calculator).sum(not(anyNumber()), any()).returns(10)

calculator.sum('5', 2) // returns 10
calculator.sum(5, 2) // returns undefined

And

const calculator = mockt(Calculator)
when(calculator).sum(and(neq(2), neq(3)), any()).returns(10)

calculator.sum(1, 2) // returns 10
calculator.sum(2, 2) // returns undefined
calculator.sum(3, 2) // returns undefined

Or

const calculator = mockt(Calculator)
when(calculator).sum(or(2, 3), any()).returns(10)

calculator.sum(2, 2) // returns 10
calculator.sum(3, 2) // returns 10
calculator.sum(1, 2) // returns undefined

Verification

Verify method called

const calculator = mockt(Calculator)

calculator.sum(1, 2)

verify(calculator).sum(1, 2) // passes
verify(calculator).sum(any(), 2) // passes
verify(calculator).sum(2, 2) // fails

Verify property read

const user = mockt(User)

const name = user.name

verify(user).getProperty('name') // passes
verify(user).getProperty('lastname') // fails

Verify property set

const user = mockt(User)

user.name = 'Alice'

verify(user).setProperty('name', 'Alice') // passes
verify(user).setProperty('name', any()) // passes
verify(user).setProperty('name', 'Bob') // fails

Verify method called at least n times

const calculator = mockt(Calculator)

calculator.sum(1, 2)
calculator.sum(3, 4)
calculator.sum(5, 6)

verifyAtLeast(2, calculator).sum(any(), any()) // passes
verifyAtLeast(3, calculator).sum(any(), any()) // passes
verifyAtLeast(4, calculator).sum(any(), any()) // fails

Verify method called at most n times

const calculator = mockt(Calculator)

calculator.sum(1, 2)
calculator.sum(1, 3)
calculator.sum(1, 4)

verifyAtMost(4, calculator).sum(1, any()) // passes
verifyAtMost(3, calculator).sum(1, any()) // passes
verifyAtMost(2, calculator).sum(1, any()) // fails

Verify method called exactly n times

const calculator = mockt(Calculator)

calculator.sum(1, 2)
calculator.sum(1, 3)
calculator.sum(1, 4)

verifyTimes(3, calculator).sum(1, any()) // passes
verifyTimes(2, calculator).sum(1, any()) // fails

Verify method never called

const calculator = mockt(Calculator)

calculator.sum(1, 2)
calculator.sum(1, 3)
calculator.sum(1, 4)

verifyNever(calculator).sum(1, 1) // passes
verifyNever(calculator).sum(1, 2) // fails

Verify multiple method calls

const calculator = mockt(Calculator)

calculator.sum(1, 2)
calculator.sum(1, 3)
calculator.sum(1, 4)

verifyMulti(calculator)
    .sum(1, 2)
    .sum(1, 4)
    .called() // passes

Verify multiple methods never called

const calculator = mockt(Calculator)

calculator.sum(1, 2)
calculator.sum(1, 3)
calculator.sum(1, 4)

verifyMulti(calculator)
    .sum(2, 2)
    .sum(2, 4)
    .never() // passes

Verify multiple methods called in expected order

const calculator = mockt(Calculator)

calculator.sum(1, 2)
calculator.sum(1, 3)
calculator.sum(1, 4)

verifyMulti(calculator)
    .sum(1, 2)
    .sum(1, 4)
    .calledInOrder() // passes
const calculator = mockt(Calculator)

calculator.sum(1, 2)
calculator.sum(1, 3)
calculator.sum(1, 4)

verifyMulti(calculator)
    .sum(1, 4)
    .sum(1, 2)
    .calledInOrder() // fails

Verify sequence of calls from different mocks or spies

import { verifySequence } from './verifySequence'

const billingService = mockt<BillingService>()
const deliveryService = mockt<DeliveryService>()

billingService.createFor(customer, products)
deliveryService.deliveryFor(customer.address, products)

verifySequence()
    .call(billingService).createFor(any(), any())
    .call(deliveryService).deliveryFor(any(), any()) // passes

Capture arguments

Last call

const calculator = mockt(Calculator)

calculator.sum(1, 2)
calculator.sum(1, 3)

const [first, second] = capture(calculator).sum // returns [1, 3]
const calculator = mockt(Calculator)

calculator.sum(1, 2)
calculator.sum(1, 3)

const [first, second] = captureLast(calculator).sum // returns [1, 3]

First call

const calculator = mockt(Calculator)

calculator.sum(1, 2)
calculator.sum(1, 3)

const [first, second] = captureFirst(calculator).sum // returns [1, 2]

All calls

const calculator = mockt(Calculator)

calculator.sum(1, 2)
calculator.sum(1, 3)

const args = captureAll(calculator).sum // returns [[1, 2], [1, 3]]

Setters

const user = mockt(User)

user.name = 'Alice'

const [name] = capture(user).setProperty('name') // returns 'Alice'

Spies

const calculator = new Calculator()
const calculatorSpy = spy(calculator)

calculator.sum(1, 2) // returns 3

verify(calculatorSpy).sum(1, 2) // passes
verify(calculatorSpy).sum(2, 2) // fails

Reset

Calls

const calculator = mockt(Calculator)
calculator.sum(1, 2) // returns undefined
verify(calculatorSpy).sum(1, 2) // passes

resetCalls(calculator)

verify(calculatorSpy).sum(1, 2) // fails

All

const calculator = mockt(Calculator)
calculator.sum(1, 2) // returns undefined
verify(calculatorSpy).sum(1, 2) // passes

reset(calculator)

verify(calculatorSpy).sum(1, 2) // fails
const calculator = mockt(Calculator)
when(calculator).sum(2, any()).returns(3)
reset(calculator)

calculator.sum(1, 2) // returns undefined

Credits

Thanks to all the contributors of ts-mockito and Mockk.

About

Joyful mocking library for Typescript and Javascript

Resources

License

Stars

Watchers

Forks