In my project I have a class SessionManager which sets and clears session variables for sticky forms etc. Every method in that class takes in a Silex\Application object as parameter. How can I unit test these methods? Create a Silex application object in each test method?
I'm new to Silex and unit testing so and not sure how to handle this.
Example of one method:
public static function setMessage($message, $messageClass, Application &$app)
{
// store message as array, with content and class keys
$app['session']->set('message', array(
'content' => $message,
'class' => $messageClass
));
}
Firstly I think that $app should not be a dependency of your SessionManager; $app['session'] should be. But also it should be a dependency of the object (ie: passed to the constructor) not of the individual method.
But that doesn't change the tactic for solving the problem. All you need to do is create a mock of the Session, which covers the dependent method you need to call, eg:
// in your test class
// ...
public function setup(){
$mockedSession = $this->getMockedSession();
$this->sessionManager = new SessionManager($mockedSession);
}
public function testMessage(){
// test stuff here
}
private function getMockedSession(){
$mockedSession = $this->getMockBuilder('\Symfony\Component\HttpFoundation\Session\Session')
->disableOriginalConstructor()
->setMethods(['set'])
->getMock();
$mockedSession->method('set')
->willReturn('something testable');
return $mockedSession;
}
You probably only need to test that your setMessage method passes through its $message and $messageClass values to the mocked method: that's all it's doing. In this light you probably wanna have a ->with('message', [$testMessage, $testClass]) or something in your mock too. The exact implementation is down to how you want to test.
Related
I need to mock a zf2 controller and keep one real function: "getStopWords".
I tried this answer adapted below:
public function createMockExecpt()
{
// create mock to get names of all functions
$mock = $this->getMockBuilder('Controller\CollectionsController')->disableOriginalConstructor()->getMock();
$reflection = new MyReflectionClass($mock);
$functionsToMock = $reflection->getAllfunctionNamesExcept(["getStopWords"]);
// create mock but don't mock one function
return $this->getMock('Controller\CollectionsController', $functionsToMock);
}
but got an error about redefining a class.
// Cannot redeclare Mock_CollectionsController_d61a5651::__clone()
I think this happens because I need an instance of the controller to find out all the functions it has. However I can't make an instance of the controller in this context, which is why I need a mock. But I can't make more than one mock of a class within the same test, so I'm stuck.
My problem was that I thought you need an instance of the class to get all the methods.
Turns out all you need is the class name!
public function testGetStopWords()
{
// get the class methods the controller has, except getStopWords
$methodsToMock = array_diff(
get_class_methods("Controller\CollectionsController"),
["getStopWords"]
);
// use setMethods to determine which methods to mock
$mockController = $this->getMockBuilder("Controller\CollectionsController")
->setMethods($methodsToMock)
->disableOriginalConstructor()
->getMock();
}
So I'm writing tests for a php api consumer library. In one of the main libraries main functions I have:
public function __call($name, $args) {
return new Schema($name, $this);
}
In my test I'm using mockery and doing something like:
$schemaMock = m::mock('overload:Mynamespace\Schema');
this is properly overloading my Schema class with a mock. So later when I do:
$myclass->movies()
It should call the __call method, thus calling the mocked Schema class. This all seems good so far, but I would like to assert that the $schemaMock is being constructed with the name of the function, in this case movies as well as the instance of the class being passed in. What I've tried is:
$schemaMock->shouldReceive('__construct')->with('movies');
However my tests pass regardless of what the "with" function argument states. IE I can change movies to foobar and tests still pass. I'm sure I'm missing something simple about how to run these assertions. Thanks for any help!
Short and final answer: you can't.
A mock is defined by cobbling together code for a new class. It contains methods for all mocked methods, and for all others, it is a child class that implements the original class as its parent. The mock class contains no constructor - for normal mock objects, the parent gets called. That is the way constructor arguments passed to Mockery::mock($class, $args) get considered.
But for instance mocks, a constructor for copied mocks is needed. It is hidden pretty deep inside the source, but if you look at mockery/library/Mockery/Generator/StringManipulation/Pass/InstanceMockPass.php, you can see that the only purpose of that method is to copy properties and expectations over to the new instance. Arguments are simply ignored.
The best approximation you could get would be to wrap your instantiation call in a mockable method that does nothing besides creating an instance with the arguments from the method - like here:
public function instantiate ( $name, $args=array() ) {
if ( empty($args) )
return new $name();
else {
$ref = new ReflectionClass( $name );
return $ref->newInstanceArgs( $args );
}
}
or to delegate the real instantiation task from the constructor to a mockable method:
class Schema {
public function __construct () {
call_user_func_array( array( $this, 'init' ), func_get_args() );
}
public function init ($name, $obj) {
//...
}
}
I'm admittedly very rusty with unit testing.
I have the following function in my Registration.php:
protected function _showModal($request) {
if (Store::isUserSupported()) {
return false;
}
return true;
}
I started writing the following test but I know I'm missing some key items:
public function testUserSupported() {
$mockStore = $this->getMockClass('Store', array('isUserSupported'));
$mockStore::staticExpects($this->once())
->method('isUserSupported')
->will($this->returnValue(true));
$mockStore::isUserSupported();
$plugin = $this->getMockBuilder('Registration')
->setMethods(array('_showModal'))
->disableOriginalConstructor()
->getMock();
$plugin = $this->getPublicClass($plugin);
$plugin->expects($this->once())
->method('_showTermsModal')
->will($this->returnValue(true));
}
The $mockStore part is getting called, but not sure how to tie it into my $plugin call.. I want to write a test to mock Store::isUserSupported() returning true within the showModal function. Any words of advice?
You should avoid static calls in your code, because they make your code coupled together and hard to change and hard to maintain. It also makes it harder to tests, as you are suffering here.
Instead of static calls, pass the collaborators needed to work. In your code example, the class you want to test would receive as parameter the Store class, instead of calling it statically.
By doing this change, you can now create a mock for the method isUserSupported() of the Store class, and pass the mock to the object under test, that will efectively use the mocked object.
$mockStore = $this->getMock('Store', array('isUserSupported'));
$mockStore->expects($this->once())
->method('isUserSupported')
->will($this->returnValue(true));
$object_under_test = new Registration( $mockStore );
$object_under_test->showModal($request); // Only in case showModal is public, otherwise call public method
If the Store class is only needed for that method, and you don't want to pass it as dependency in the constructor, you can pass it on the method itself.
$mockStore = $this->getMock('Store', array('isUserSupported'));
$mockStore->expects($this->once())
->method('isUserSupported')
->will($this->returnValue(true));
$object_under_test = new Registration();
$object_under_test->showModal($mockStore, $request); // Only in case showModal is public, otherwise call public method
Also, you shouldn't test protected/private methods of your classes, since they are low level implementation details that your tests don't need to know about. You should only make calls to public methods in your tests. Otherwise, the tests become very coupled with the real implementation, and if you refactor your code, you will most likely have to change the tests too.
I'm trying to create a pretty standard unit test where I call a method and assert it's response, however the method I'm testing calls another method inside the same class which does a little bit of heavy lifting.
I want to mock that one method but still execute the method I'm testing as is, only with the mocked value returned from the call to the other method.
I've dumbed down the example to make it as simple as possible.
class MyClass
{
// I want to test this method, but mock the handleValue method to always return a set value.
public function testMethod($arg)
{
$value = $arg->getValue();
$this->handleValue($value);
}
// This method needs to be mocked to always return a set value.
public function handleValue($value)
{
// Do a bunch of stuff...
$value += 20;
return $value;
}
}
My attempt at writing the tests.
class MyClassTest extends \PHPUnit_Framework_TestCase
{
public function testTheTestMethod()
{
// mock the object that is passed in as an arg
$arg = $this->getMockBuilder('SomeEntity')->getMock();
$arg->expects($this->any())
->method('getValue')
->will($this->returnValue(10));
// test handle document()
$myClass = new MyClass();
$result = $myClass->testMethod($arg);
// assert result is the correct
$this->assertEquals($result, 50);
}
}
I have tried mocking the MyClass object, but when I do that and call the testMethod it always returns null. I need a way to mock the one method but leave the rest of the object intact.
You can mock the class that you are testing and specify the method that you want to mock.
$mock = $this->getMockBuilder('MyClass')
->setMethods(array('handleValue'))
->getMock();
$mock->expects($this->once())
->method('handleValue')
->will($this->returnValue(23)) //Whatever value you want to return
However, IMO this is not the best idea for your tests. Testing like this will make refactoring much more difficult. You are specifying the implementation of the class rather than the behavior that the class is supposed to have. If handleValue is doing a lot of complicated work that makes testing difficult, consider moving the logic into a separate class and injecting that into your class. Then you can create a mock of that class and pass it in to testMethod. Doing so will give you the added advantage of making MyClass more extensible if handleValue needs to adapt its behavior.
http://www.oodesign.com/strategy-pattern.html
As a general rule, you should not mock the system that you are testing.
You can specify which methods to mock (partial mock) with setMethods():
// Let's do a `partial mock` of the object. By passing in an array of methods to `setMethods`
// we are telling PHPUnit to only mock the methods we specify, in this case `handleValue()`.
$csc = $this->getMockBuilder('Lightmaker\CloudSearchBundle\Controller\CloudSearchController')
->setConstructorArgs($constructor)
->setMethods(array('handleValue'))
->getMock();
// Tell the `handleValue` method to return 'bla'
$csc->expects($this->any())
->method('handleValue')
->with('bla');
Any other methods in the class not specified in the array you give setMethods() will be executed as is. If you do not use setMethods all methods will return NULL unless you specifically set them.
We use Varien_Http_Client to make http requests from a Magento extension, like this:
public function callApi(…)
{
<SNIP>
// Set default factory adapter to socket in case curl isn't installed.
$client = new Varien_Http_Client($apiUri, array(
'adapter' => 'Zend_Http_Client_Adapter_Socket',
'timeout' => $timeout,
));
$client->setRawData($xmlDoc->saveXML())->setEncType('text/xml');
$response = $client->request($method);
$results = '';
if ($response->isSuccessful()) {
$results = $response->getBody();
}
return $results;
}
I understand I should avoid testing the internals of Varien_Http_Client; rather, I should test that we are sending it the right inputs, and handling its outputs correctly. I can mock Varien_Http_Client easily enough, but even if I refactor this code to let me replace the Varien_Http_Client with its mock, I don't understand how to generally* test that the constructor was called with the expected arguments, since the constructor is called by PHPUnit::getMock.
I don't need a mock object; I need a mock class. How can I test that a constructor was called with expected arguments?
* (In this case I know ways to work around this problem specific to Varien_Http_Client, but what can I do with more opaque third-party code?)
This is what we call "untestable" code. When you build dependencies inside your methods, there is no way to mock them. Every use of "new" keyword in your model is a signal that you should consider injecting the object instead of create it inside. In my opinion, the only exception from that rule is when you create a "data container" object or factory class. But in these cases probably you can test the object because methods will return it.
So as you said, the method you showed need a little refactor, for example:
class ApiClass
{
protected $client;
public function __construct(Varien_Http_Client $client)
{
$this->client = $client;
}
public function callApi()
{
$this->client->setRawData($xmlDoc->saveXML())->setEncType('text/xml');
(...)
Best!