PHPUnit returnCallback

What I wanted to test

In my project, I have a solr query object that holds some query parameters. Another object - called query modifier - sets parameters on the query object depending on a given array of settings.

My test should check, whether the modifier sets the expected parameters on the query object.

 

Writing a test independent of implementation details

Since the order in which the parameters are set within the modifier are an implementation detail of the modifier, I did not want to rely on any order in the test.

Let's assume, we had the order, in which the parameters are set - and the order should be part of the test - then we could use assertions similar to this snippet:

/** @test */
public function modifierSetsParametersOnSolrQueryAsExpected() {
	$dataBackendMock = $this->getDataBackendMockWithTsFakeSettings();

	$solrQueryMock = $this->getMock('tx_solr_Query', array('addQueryParameter'), array(), '', FALSE);
	$solrQueryMock->expects($this->at(1))->method('addQueryParameter')->with('hl', 'true');
	$solrQueryMock->expects($this->at(2))->method('addQueryParameter')->with('hl.fl', '');
	$solrQueryMock->expects($this->at(3))->method('addQueryParameter')->with('hl.snippets', '1');
	$solrQueryMock->expects($this->at(4))->method('addQueryParameter')->with('hl.fragsize', '100');

	$highlightModifier = new Tx_PtSolr_Extlist_DataBackend_QueryModifier_HighlightingModifier();
	$highlightModifier->injectDataBackend($dataBackendMock);
	$highlightModifier->modifyQuery($solrQueryMock);
}

Joachim and I came up with the following solution which is independent of any order of function-calls and shows a nice usage of closures in unit tests:

class Tx_PtSolr_Tests_Unit_Extlist_DataBackend_QueryModifier_HighlightingModifierTest 
		extends Tx_PtSolr_Tests_BaseTestcase {

	private $tsFakeSettings = array(
		'backendConfig' => array(
			'highlighting' => array(
				'enable' => '1',
				'hl' => array (
					'fl' => '*',
					'snippets' => '1',
					'fragsize' => '100'
				),
				'simple_pre' => '<strong>',
				'simple_post' => '</strong>',
				'useOriginalSearchWordForHighlighting' => '1'
			)
		)
	);



	private $fakeSearchWords = 'testword';



	/** @test */
	public function modifierSetsParametersOnSolrQueryAsExpected() {
		$dataBackendMock = $this->getDataBackendMockWithTsFakeSettings();

		$setParams = array();
		$solrQueryMock = $this->getMock(
			'tx_solr_Query', 
			array('addQueryParameter'), 
			array(), '', FALSE
		);

		$solrQueryMock
				->expects($this->any())
				->method('addQueryParameter')
				->will($this->returnCallback(
					function($key, $value) use (&$setParams) {
						$setParams[$key] = $value;
					}
				)
		);

		$highlightModifier = new Tx_PtSolr_Extlist_DataBackend_QueryModifier_HighlightingModifier();
		$highlightModifier->injectDataBackend($dataBackendMock);
		$highlightModifier->modifyQuery($solrQueryMock);

		$this->assertTrue($setParams['hl'] == 'true');
		$this->assertTrue($setParams['hl.fl'] == '*');
		$this->assertTrue($setParams['hl.snippets'] == '1');
		$this->assertTrue($setParams['hl.fragsize'] == '100');
		$this->assertTrue($setParams['hl.simple.pre'] == '<strong>');
		$this->assertTrue($setParams['hl.simple.post'] == '</strong>');
		$this->assertTrue($setParams['hl.q'] == $this->fakeSearchWords);
	}



	/**
	 * Returns mocked data backend returning fake ts settings
	 *
	 * @return Tx_PtSolr_Extlist_DataBackend_SolrDataBackend
	 */
	private function getDataBackendMockWithTsFakeSettings() {
		$dataBackendMock = $this->getMock(
			'Tx_PtSolr_Extlist_DataBackend_SolrDataBackend', 
			array('getDataBackendSettings','getSearchWords'), 
			array(),
			 '', 
			FALSE
		);
		$dataBackendMock
			->expects($this->any())
			->method('getDataBackendSettings')
			->will($this->returnValue($this->tsFakeSettings));

		$dataBackendMock
			->expects($this->once())
			->method('getSearchWords')
			->will($this->returnValue($this->fakeSearchWords));

		return $dataBackendMock;
	}

}

As the whole test class is rather long, we inspect the relevant lines in more detail:

$solrQueryMock
		->expects($this->any())
		->method('addQueryParameter')
		->will($this->returnCallback(
			function($key, $value) use (&$setParams) {
				$setParams[$key] = $value;
			}
		)
);

On the solr query mock object, we add a method 'addQueryParameter' that will return a callback method, whenever the method is called on the object. The returned method is implemented as a closure, getting two arguments ($key and $value) and we give it a variable within its scope ($setParams).

Whenever addQueryParameter($key, $value) is called on the original object, we will now call our anonymous function function($key, $value) which will set some values on the given variable $setParams. Mind that we have to use $setParams as a reference in order to be able to actually write on the variable within our closure.

After that we can easily check on the values written in $setParams without depending on the order of which the values are written.

Thanks to Joachim for helping me out on this one!

Further information

Some further information on this stuff can be found here: 
http://www.selfcontained.us/2011/05/28/phpunit-mocks-and-closures/

 


 
Inhalt © Michael Knoll 2009-2017  •  Powered by TYPO3  •  TypoScript Blogging by Fabrizio Branca  •  TYPO3 Photo Gallery Management by yag  •  Impressum