I am new to testing with PHPUnit, and what I am trying to do is test a method called returnOnLogin() that accepts a parameter Enlight_Event_EventArgs $args and returns true.
Here's the method I want to test:
public function returnOnLogin(\Enlight_Event_EventArgs $args)
{
$controller = $args->get('subject');
$view = $controller->View();
$controller->redirect([
'controller' => 'verification'
]);
// $view->addTemplateDir(
// __DIR__ . '/Views'
// );
return true;
}
Here's my test:
class MyFirstTestPluginTest extends TestCase
{
public function testReturnOnLogin()
{
$my_plugin = new MyFirstTestPlugin(true);
$expected = true;
//I tried following but it did not work
$this->assertEquals($expected, $my_plugin->returnOnLogin(//here is the problem it requires this array that I dont know));
}
}
Assuming that your controller class is Controller, and assuming that we don't care that view() is invoked in $controller, this should cover what you are looking for:
class MyFirstTestPluginTest extends TestCase
{
public function testReturnOnLogin()
{
/**
* create a test double for the controller (adjust to your controller class)
*/
$controller = $this->createMock(Controller::class);
/**
* expect that a method redirect() is called with specific arguments
*/
$controller
->expects($this->once())
->method('redirect')
->with($this->identicalTo([
'controller' => 'verification'
]));
/**
* create a test double for the arguments passed to returnLogin()
*/
$args = $this->createMock(\Enlight_Event_EventArgs::class);
/**
* expect that a method subject() is invoked and return the controller from it
*/
$args
->expects($this->once())
->method('subject')
->willReturn($controller);
$plugin = new MyFirstTestPlugin(true);
$this->assertTrue($plugin->returnOnLogin($args));
}
}
What does this test do?
Arrange
This test first arranges test doubles for use with the system under test (your plugin).
The first test double is your controller, we set it up in such a way that we expect that a method redirect() is invoked once with an argument that is identical to the specified array.
The second test double is the argument, we set it up in such a way that we expect that a method 'subject()` is invoked one, and will return the controller.
Then, we set up the system under test, simply by creating an instance of MyFirstTestPlugin, passing true to the constructor.
Unfortunately, you haven't shared the constructor with us, we have no idea what the argument true stands for. If it affects the behaviour of returnLogin(), then we clearly need to add more tests to assert the behaviour when the argument takes different values.
Act
Then this test invokes the method returnLogin() on the system under test, and passes in one of the test doubles.
Assert
Eventually, this test asserts that the method returnLogin() returns true.
Note Take a look at
http://wiki.c2.com/?ArrangeActAssert
https://phpunit.de/manual/current/en/test-doubles.html
Related
I use simple-phpunit from phpunit/phpunit-bridge by and with Symfony here.
I have a class A, that I desire to test, which uses a class B method in it.
While mocking the B and it's method I realise it is called multiple time and with multiple parameters : a class name and a integer.
I use returnValueMap() for this but have a problem : PHPUNIT seem to require that I precisely set the parameters, to their exact value when the method shall be called.
I can set the class name ( there are a given possible number fo them , so I can set them manually) but the given integer is arbitrary ( and the method is called in a loop ) , therefore I cannot set it precisely.
Is it possible to tell PHPUNIT to accept any parameter to this function ?
Like a anyParameter() method ( that I did not find after scouring the TestCase file ).
If I set the Integer parameter to a fixed value in the test and the class method, the test pass ( but of course I can't set the Integer as a fixed value in the class method ).
class ToBeTested{
public $objectB;
public function functionToTest($dataList){
......
$objectB=new ObjectB();
foreach($dataList as $data){
$className=$data['className'];
$integerNeeded=$data['integer'];
//Here if $inetegerNeeded is not the exact value set in the Mock
//$result will be null
$result=$this->objectB->doSomething($className,$integerNeeded);
if($result == null) throw new \Excpetion(" Object is null");
}
.....
}
}
class UnitTest extends TestCase{
/**
$generatedDataList comes from a DataProvider
with some varied and exotic data close to real use cases
**/
public function testFunction($generatedDataList)
{
$mockOfObjectB= $this->getMockBuilder(ObjectB::class)
->getMock();
/**
Here I'd like to use $this->anyParameter() or something that'd work that way
If I set this to a fixed value here and in the functionToTest() method it passes
**/
$anyInteger=-1;
$dataReferenceMap= [
['ClassNameA',$anyInteger,new MockObject()],
['ClassNameB',$anyInteger,new MockObject()],
['ClassNameC',$anyInteger,new MockObject()],
];
$mockOfObjectB
->expects($this->any())
->method('doSomething')
->will($this->returnValueMap($dataReferenceMap));
$objectToTest = new ToBeTested();
$objectToTest->objectB=$mockOfObjectB;
//DO THE TESTING - will because of the parameter Fail
$objectToTest->functionToTest($generatedDataList);
}
}
}
Might I also be wrongly returnValueMap() ?
It's quite new to me and I think the docs on it are pretty scarce and don't explain really well how to use it.
I would go with willReturnCallback. Here is the example (checked with phpunit 8.1)
<?php
declare(strict_types = 1);
use PHPUnit\Framework\TestCase;
final class SomeTest extends TestCase
{
public function testSome()
{
$mock = $this
->getMockBuilder(\stdClass::class)
->setMethods(['doSomething'])
->getMock();
$mock->method('doSomething')
->willReturnCallback(
function (string $classname, int $int) {
$map = [
'ClassNameA' => 123,
'ClassNameB' => 345,
// ...
];
return $map[$classname];
}
);
$this->assertSame(123, $mock->doSomething('ClassNameA', 1));
$this->assertSame(123, $mock->doSomething('ClassNameA', 1111111));
$this->assertSame(345, $mock->doSomething('ClassNameB', 13456));
$this->assertSame(345, $mock->doSomething('ClassNameB', 999999));
}
}
It is a shortcut for ->will($this->returnCallback(...)). The doc
I get an error:
Type error: Too few arguments
I thought Laravel do some magic to if arguments is not fully passed?
For example:
In the Controller I have:
public function create(CreateRequest $request)
{
return $this->todoService->createList($request);
}
In the todoService class:
use App\Plan
class todoService {
public function createList($request, Plan $plan)
{
//
}
}
As you can see I did not pass Plan class object. Do I have to bind or something?
If you are calling createList() by yourself so you will need to pass both parameters by yourself. You can bind something with Plan but still if you will call something not Laravel, then you will be responsible to pass that function parameters.
This type hinting only works if Laravel is calling that function. So you need to call these functions through Laravel.
If you are trying to automatically injecting in a class's constructor, you can simply do this:
$service = $this->app->make('App\Plan\todoservice');
or
$service = App::make('App\Plan\todoservice');
or
$service = resolve('App\Plan\todoservice');
But this will only work for Constructors. Please note that you can also provide parameters as next arguments of make() or resolve() function.
In fact, different methods can also be called that way.
You can simply do:
$service = App::make('App\Plan\todoservice');
$container->call([$service, 'createList'], ['request' => $request] );
Here $container is object of Illuminate\Contracts\Container\Container.
You have to bind classes only if they depend on interfaces. If you specify particular class, reflection will do the job for you. documentation
The only way this will work, is to set the default value of second parameter. In any other situation, syntax exception will be thrown.
use App\Plan
class todoService
{
public function createList($request, Plan $plan = null)
{
//
}
}
public function create(CreateRequest $request)
{
return $this->todoService->createList($request);
}
It will work, but will that make any sense?
Laravel cannot do any magic on this level as your coding error is simply a PHP syntax error. You're indicating that the second parameter is of type Plan, which makes it mandatory implicitly. Laravel cannot 'patch' simple function calls like this.
Your confusion is likely in that, depending on your routing, Laravel can inject the correct Plan parameter into the create controller method, thus allowing you to forward it into the service.
/**
* Create a new personal access token for the user.
*
* #param string $name
* #param array $scopes
* #return \Laravel\Passport\PersonalAccessTokenResult
*/
public function createToken($name, array $scopes = [])
{
return Container::getInstance()->make(PersonalAccessTokenFactory::class)->make(
$this->getKey(), $name, $scopes
);
}
/**
* Set the current access token for the user.
*
* #param \Laravel\Passport\Token $accessToken
* #return $this
I have a class that has two public method. It looks something like following:
class IntRequest
{
public function updateStatus()
{
$isValid = $this->checkValidity();
// ... next is a complex logic that use $isValid
}
/**
* #return bool
*/
public function isValid()
{
// another complex logic
}
}
I need to test a first function - IntRequesr::updateStatus; however I need to run to tests. The first one with IntRequests::isValid returns false and the second one with true as a result of IntRequests::isValid
I try to mock that function but tests run with calling actual IntRequests::isValid not mocked one.
My testing code is
$intRequest = new IntRequests;
$mock = m::mock($intRequest);
$mock->shouldReceive('isValid')
->once()
->andReturn(true);
$res = $mock->updateStatus();
$this->assertTrue($res);
I've try to call $res = $intRequest->updateStatus() instead of $res = $mock->updateStatus() but with no luck.
So, I am wondering is it possible to mock function that is called inside testing method?
You need a partial mock (a mock object, in which some of the methods are stubbed, while the rest are left as is). Since I've done such only with the phpunit's own mock library, I can only point you to the documentation, but it seems that you should just add ->makePartial() to your mock instantiation
In CakeDC comments plugin for CakePHP documentation states that:
Component callbacks
It is possible to override or extend the most comments component
methods in the controller. To do this we need to create method with
prefix callback_comments Examples:
callback_add will named as callback_commentsAdd in controller,
callback_fetchData will named as callback_commentsFetchData in
controller. ...
It works from the controller perfectly!:
public function callback_commentsInitType() {
return 'flat'; // threaded, tree and flat supported
}
I wonder, what is the new feature of cakephp-2.0 that allows you to do that? I need to understand how it was achieved to be able to implement such methodology in the future on my components.
In the code of the component, if you look at the following function in this file (starting from line number 622):
/**
* Call action from commponent or overriden action from controller.
*
* #param string $method
* #param array $args
* #return mixed
*/
protected function _call($method, $args = array()) {
$methodName = 'callback_comments' . Inflector::camelize(Inflector::underscore($method));
$localMethodName = 'callback_' . $method;
if (method_exists($this->Controller, $methodName)) {
return call_user_func_array(array(&$this->Controller, $methodName), $args);
} elseif (method_exists($this, $localMethodName)) {
return call_user_func_array(array(&$this, $localMethodName), $args);
} else {
throw new BadMethodCallException();
}
}
You can see that the variable $methodName is being defined with prefix callback_comments and then the passed $method is appended to it after being treated by the Inflector::underscore and then Inflector::camelize method. The working of these is as follows:
Inflector::underscore will convert initType to init_type. Check doc here.
Inflector::camelize will further convert init_type to InitType. Check doc here.
Now, if initType was passed in the argument, then the $methodName will be:
callback_comments + InitType = callback_commentsInitType
After this, a $localMethodName is also being generated. In our initType example, it will be:
callback_ + initType = callback_initType
After the names have been generated, it will simply search if the method exists in the attached controller and will execute it using call_user_func_array function by passing it and array with the object (in our case, the controller object (&$this->Controller) or the component object itself (&$this)) containing the method and the $methodName as first argument and then $args as second argument.
If the function was not found in the controller, then it will instead search in the component with $localMethodName. If it's found, then it is executed the same way.
Now how all this works is that the _call function is the single function used to call all the internal functions of the component, so that it will first check if the function has been overwritten in the controller, otherwise it will execute the function in the component itself.
You can check the component's beforeRender function here and you'll see how the initType function is called. In this case, if the controller contains a function named callback_commentsInitType, then it will be executed. Otherwise, the components callback_initType will be executed.
Hope this helps..
If you have a class that responds differently depending upon constructor arguments, how do you go about writing a spec for that class?
class Route
{
function __construct($url, array $methods = array())
{
// stores methods and url in private member variables
// creates a regex to match $url against incoming request URLs
}
public function isMatch($url)
{
// checks if the incoming request url matches against this url
}
}
Example use:
$a = new Route('/users/:id');
$a->isMatch('/users/1') // returns true;
$b = new Route('/users');
$b->isMatch('/users') // returns true
If I set up my spec for this class using the let function from phpspec:
class Route extends ObjectBehaviour
{
function let()
{
$this->beConstructedWith('/users/:id')
}
}
My spec can only check if the behaviour of this class works in one of the cases.
I've contemplated adding setter methods to allow me to test around this, but it seems like I'd be breaking encapsulation for the purpose of testing.
I'm struggling to find anything that touches upon this, so I'm started to think that maybe this is bad code smell situation.
beConstructedWith() doesn't always need to be called from the let() method. You can call it from the specs as well.
In my opinion there's nothing wrong in setting up an object in more than one way. However, you should avoid doing too much work in the constructor.
Constructor should be used only to obtain variables that will be set to a member properties here. No further logic should be done here...
Following the idea from point 1 there should be another logic that determines what happens next (e.g. if Object->hasProperty(X) then do x(), etc.)
Then a comment would be plain and straight forward.
Example:
class Route
{
private $url;
private $methods = array();
/**
* Constructor method, sets the attributes to private member variables
* #param string $url URL pattern
* #param array $methods Methods that should be used with given URL
*/
function __construct($url, $methods = array())
{
$this->url = $url;
$this->methods = $methods;
}
// ...
}