A talk by @thorstenfrommen
The process consisting of all life cycle activities, both static and dynamic, concerned with planning, preparation and evaluation of software products and related work products to determine that they satisfy specified requirements, to demonstrate that they are fit for purpose and to detect defects. ISTQB®
There are several things you might be able to test:
Also component testing or module testing.
The process of evaluating a system or component during or at the end of the development process to determine whether it satisfies specified requirements. IEEE Std 610.12-1990
Unit testing is applying white-box testing techniques,
meaning close examination of procedural level of detail.
Designing tests requires full knowledge of internals.
function get_answer(): string {
return '42';
}
To test if a function behaves as expected, one has to know what to expect.
Possible units in code:
Logic is testable, and so are functions and classes/modules.
Going even higher to namespaces, packages and so on is no good.
Define a unit as either logic, or function, or class/module.
function doubleOrHalf( number ) {
if ( number > 0 ) {
return number * 2;
}
return number / 2;
}
Logic very often not testable as individual unit.
⇒ A unit is either a function, or a class/module.
Important! Often overlooked.
or
function test_register_taxonomy() {
$tax = rand_str();
$this->assertFalse( taxonomy_exists( $tax ) );
register_taxonomy( $tax, 'post' );
$this->assertTrue( taxonomy_exists( $tax ) );
$this->assertFalse( is_taxonomy_hierarchical( $tax ) );
unset( $GLOBALS['wp_taxonomies'][ $tax ] );
}
This is not a unit test. But it has every right to exist.
function get_answer(): string {
return '42';
}
export function getAnswer() {
return '42';
}
class FunctionsTest extends \PHPUnit\Framework\TestCase {
public function test_get_answer_return_expected_answer() {
$expected = '42';
$actual = get_answer();
$this->assertSame( $expected, $actual );
}
}
describe( 'getAnswer()', () => {
test( 'should return expected answer', () => {
const expected = '42';
const actual = getAnswer();
expect( actual ).toBe( expected );
} );
} );
class Oracle {
public function answer(): string {
return '42';
}
}
class Oracle {
answer() {
return '42';
}
}
export default Oracle;
class OracleTest extends \PHPUnit\Framework\TestCase {
public function test_return_expected_answer() {
$this->assertSame( '42', ( new Oracle() )->answer() );
}
}
describe( 'Oracle', () => {
test( 'should return expected answer', () => {
expect( ( new Oracle() ).answer() ).toBe( '42' );
} );
} );
class FortuneTeller {
private $oracle;
public function __construct( Oracle $oracle ) {
$this->oracle = $oracle;
}
public function answer( int $money = 0 ): string {
return ( $money >= 5 ) ? $this->oracle->answer() : '';
}
}
class FortuneTeller {
constructor( oracle ) {
this.oracle = oracle;
}
answer( money = 0 ) {
return ( Number( money ) >= 5 ) ? this.oracle.answer() : '';
}
}
export default FortuneTeller;
🙀
Define dummy functions and objects that you have absolute control over.
Take care of two things:
class FortuneTellerTest extends \PHPUnit\Framework\TestCase {
use \Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration;
public function test_return_expected_answer() {
$answer = 'some answer here';
$oracle = \Mockery::mock( Oracle::class, [
'answer' => $answer,
] );
$fortune_teller = new FortuneTeller( $oracle );
$this->assertSame( '', $fortune_teller->answer( 0 ) );
$this->assertSame( $answer, $fortune_teller->answer( 100 ) );
}
}
class FortuneTellerTest extends \PHPUnit\Framework\TestCase {
use \Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration;
/** @dataProvider provide_answer_data */
public function test_return_expected_answer( string $expected, int $money, string $answer ) {
$oracle = \Mockery::mock( Oracle::class, [
'answer' => $answer,
] );
$this->assertSame( $expected, ( new FortuneTeller( $oracle ) )->answer( $money ) );
}
public function provide_answer_data() { /* ... */ }
}
class FortuneTellerTest extends \PHPUnit\Framework\TestCase {
// ...
public function provide_answer_data() {
return [
'not enough money' => [
'expected' => '',
'money' => 0,
'answer' => 'some answer here',
],
'enough money' => [
'expected' => 'some answer here',
'money' => 100,
'answer' => 'some answer here',
],
// ...
];
}
}
describe( 'FortuneTeller', () => {
test( 'should return expected answer', () => {
const answer = 'some answer here';
const oracle = {
answer: () => answer,
};
const fortuneTeller = new FortuneTeller( oracle );
expect( fortuneTeller.answer( 0 ) ).toBe( '' );
expect( fortuneTeller.answer( 100 ) ).toBe( answer );
} );
} );
import each from 'jest-each';
describe( 'FortuneTeller', () => {
const answer = 'some answer here';
const oracle = {
answer: () => answer,
};
each( [
[ '', 0 ],
[ '', '' ],
[ answer, 5 ],
[ answer, 100 ],
// ...
] ).test( 'should return expected answer', ( expected, money ) => {
const actual = ( new FortuneTeller( oracle ) ).answer( money );
expect( actual ).toBe( expected );
} );
} );
class TranslatingOracle {
public function answer(): string {
return __( 'The answer is 42.', 'some-textdomain-here' );
}
}
class TranslatingOracle {
answer() {
return wp.i18n.__( 'The answer is 42.', 'some-textdomain-here' );
}
}
export default TranslatingOracle;
class TranslatingOracleTest extends \PHPUnit\Framework\TestCase {
// Tear down Brain Monkey...
public function test_return_number() {
$answer = 'translated answer here';
\Brain\Monkey\Functions\expect( '__' )
->once()
->with( \Mockery::type( 'string' ), 'some-textdomain-here' )
->andReturn( $answer );
$this->assertSame( $answer, ( new TranslatingOracle() )->answer() );
}
}
describe( 'TranslatingOracle', () => {
test( 'should return expected answer', () => {
const translation = 'some translation here';
global.wp = {
i18n: {
__: jest.fn( () => translation ),
},
};
expect( ( new TranslatingOracle() ).answer() ).toBe( translation );
expect( global.wp.i18n.__ ).toHaveBeenCalledTimes( 1 );
expect( global.wp.i18n.__ ).toHaveBeenCalledWith( 'The answer is 42.', 'some-textdomain-here' );
delete global.wp;
} );
} );
class HookAwareOracle {
public function answer(): string {
do_action( 'give_answer' );
return (string) apply_filters( 'answer', '42' );
}
}
class HookAwareOracleTest extends \PHPUnit\Framework\TestCase {
// Set up and tear down Brain Monkey...
public function test_return_unfiltered_answer() {
$this->assertSame( '42', ( new HookAwareOracle() )->answer() );
$this->assertSame( 1, did_action( 'give_answer' ) );
$this->assertSame( 1, \Brain\Monkey\Filters\applied( 'the_answer' ) );
}
}
import React from 'react';
const Answer = ( props ) => {
const { id, text } = props;
return (
<li className="oracle__answer">
Answer { id }: <em>{ text }</em>
</li>
);
};
export default Answer;
import React from 'react';
import TestRenderer from 'react-test-renderer';
describe( 'Answer', () => {
test( 'should render an answer according to the passed props', () => {
const id = 'some-id-here';
const text = 'Some text here.';
const testRenderer = TestRenderer.create(
<Answer id={ id } text={ text } />
);
const output = testRenderer.toJSON();
expect( output.type ).toBe( 'li' );
expect( output.props.className ).toContain( 'oracle__answer' );
expect( output.children ).toContain( id );
expect( output ).toMatchSnapshot();
} );
} );
exports[`<Answer /> should render an answer according to the passed props 1`] = `
<li
className="oracle__answer"
>
Answer
some-id-here
:
<em>
Some text here.
</em>
</li>
`;
import React, { Component } from 'react';
import Answer from './Answer';
class ReactOracle extends Component {
render() {
const { answers } = this.props;
return (
<ul className="oracle">
{ answers.map( ( answer ) => (
<Answer key={ answer.id } { ...answer } />
) ) }
</ul>
);
}
}
export default ReactOracle;
import React from 'react';
import { shallow } from 'enzyme';
describe( '<ReactOracle />', () => {
test( 'should render list of answers passed props', () => {
const answers = [
{ id: 23, text: 'Illuminatus!' },
{ id: 42, text: '42.' },
];
const wrapper = shallow(
<Testee answers={ answers } />
);
expect( wrapper.is( 'ul.oracle' ) );
answers.forEach( ( { id, text } ) => {
expect( wrapper.containsMatchingElement( <Answer id={ id } text={ text } /> ) );
} );
} );
} );
class WhatClassTest extends \PHPUnit\Framework\TestCase {
public function test_what_method_should_do_what() {
$expected = 'What is the expected result?';
$actual = 'What is the actual result?';
$this->assertSame( $expected, $actual, 'What should it do? (optional)' );
}
}
describe( 'What are you testing?', () => {
test( 'What should it do?', () => {
const expected = 'What is the expected result?';
const actual = 'What is the actual result?';
expect( actual ).toBe( expected );
} );
} );
Unit testing...