I am a beginner in unit testing in CakePHP. My version of CakePHP is 2.5.2 and I am using cake test suite 2.5.2. I want to do controller testing. I have tried all methods given in cookbook. I think this is too much complex code for given example in cookbook. I have customised routes for my application. I can invoke method showed below by calling : http://localhost/api/v1/networks/
This is the simplest method in my controller. How can i start testing on this method and what mocks should i need?
public function index() {
if (!$this->request->is('Get')) {
throw new MethodNotAllowedException(__('HTTP request Method Not allowed..'));
}
$networks = $this->Network->UserNetwork->getNetworks($this->current_user);
$returnObject = array('message' => 'Networks found successfully',
'data' => $networks
);
return $this->_sendResponse($returnObject, 200);
}
what i have tried so far is:
<?php
App::uses('NetworksController', 'Controller');
class NetworksControllerTestCase extends ControllerTestCase {
public $fixtures = array(
'app.network',
);
public function setUp() {
parent::setUp(); // TODO: Change the autogenerated stub
$this->Network = $this->generate('Networks',
array('models' => array('network','usernetwork' => array('getNetworks'))));
}
public function testIndex() {
$result = $this->testAction('/api/v1/networks/index', array('return' => 'vars'));
debug($result);
}}
Related
I tried to test my component function through unit testing.
My component function below
public function userRole() {
$loginId = $this->Cookie->read('Admin.login_id');
$name = $this->Cookie->read('Admin.name');
$role = $this->Cookie->read('Admin.role');
if (empty($loginId) || empty($name)){
return false;
}
$adminsORM = TableRegistry::get('Admins');
$admin = $adminsORM->find('all', [
'conditions' => ['login_id' => $loginId, 'name' => $name, 'disable' => 0]
])->first();
return empty($admin)? false : $admin->role;
}
And my component testing function below
public $Acl;
public function setUp()
{
parent::setUp();
$registry = new ComponentRegistry();
$this ->Acl = new AclComponent($registry);
}
public function testUserRole()
{
// Test our adjust method with different parameter settings
$this->Cookie->write('Admin.login_id', 'demo12');
$this->Cookie->write('Admin.role', 1);
$this->Cookie->write('Admin.name', 'demo 12');
$output = $this->Acl->userRole();
$this->assertResponseOk();
}
composer testing code
vendor/bin/phpunit --filter testUserRole /d/xampp/htdocs/admin/admin/tests/TestCase/Controller/Component/AclComponentTest.php
error
Notice Error: Undefined property: App\Test\TestCase\Controller\Component\AclComponentTest::$Cookie in [D:\xampp\htdocs\admin\admin\tests\TestCase\Controller\Component\AclComponentTest.php, line 31]
As the error suggests, there is no $this->Cookie property in your unit test. I can only assume that $this->Cookie in your component refers to the Cookie component (which btw is deprecated as of CakePHP 3.5).
If you need to prepare cookies for a regular unit test, and not a controller/integration test (where you could to use the IntegrationTestCase::cookie(), IntegrationTestCase::cookieEncrypted(), IntegrationTestCase::assertResponseOk() methods), then you have to write the cookies directly to the request object, and make sure that you make it available to the component.
Check out the example in the Cookbook on how to test components, it should look something like this:
namespace App\Test\TestCase\Controller\Component;
use App\Controller\Component\MyComponent;
use Cake\Controller\Controller;
use Cake\Controller\ComponentRegistry;
use Cake\Http\ServerRequest;
use Cake\Http\Response;
use Cake\TestSuite\TestCase;
class MyComponentTest extends TestCase
{
public $component = null;
public $controller = null;
public function setUp()
{
parent::setUp();
$request = new ServerRequest();
$response = new Response();
$this->controller = $this->getMockBuilder('Cake\Controller\Controller')
->setConstructorArgs([$request, $response])
->setMethods(null)
->getMock();
$registry = new ComponentRegistry($this->controller);
$this->component = new MyComponent($registry);
}
// ...
}
You can then either define the cookies in the setUp() method, so that they are available in all tests, or you can define them individually per test. Also note that if you're working with encrypted cookies, you should use CookieCryptTrait::_encrypt() to encrypt the cookie data.
// ...
use Cake\Utility\CookieCryptTrait;
use Cake\Utility\Security;
protected function _getCookieEncryptionKey()
{
// the cookie component uses the salt by default
return Security::getSalt();
}
public function testUserRole()
{
$data = [
'login_id' => 'demo12',
'role' => 1,
'name' => 'demo 12'
];
// the cookie component uses `aes` by default
$cookie = $this->_encrypt($data, 'aes');
$request = new ServerRequest([
'cookies' => [
'Admin' => $cookie
]
]);
$this->controller->request = $request;
$output = $this->Acl->userRole();
$this->assertEquals('expected value', $output);
}
See also
Cookbook > Testing > Testing Components
API > \Cake\Utility\CookieCryptTrait
Based on the testing documentation, in order to set your cookies during your test cases, you need to use the function $this->cookieEncrypted('my_cookie', 'Some secret values'):
$this->cookieEncrypted('Admin.login_id', 'demo12');
$this->cookieEncrypted('Admin.role', 1);
$this->cookieEncrypted('Admin.name', 'demo 12');
I am using the mockery to test a method that make a lot of doctrine repository invocations with different repositories.
This is the method that i set up all my repository mocks:
public function testService()
{
$mockDoctrine = $this->getMockDoctrine();
$mockDoctrine->shouldReceive('getRepository')->once()
->andReturn($this->getRepositoryAMock());
$mockDoctrine->shouldReceive('getRepository')->once()
->andReturn($this->getRepositoryBMock());
$mockDoctrine->shouldReceive('getRepository')->once()
->andReturn($this->getRepositoryCMock());
//here is where i hit my test
$products = $this->service->fire(1, 1);
$this->assertInstanceOf('Illuminate\Support\Collection', $products);
foreach ($products as $v) {
$this->assertInstanceOf('Illuminate\Support\Collection', $v);
}
}
This is the method that i mock the Doctrine:
public function getMockDoctrine()
{
$mockDoctrine = \App::make('Doctrine');
$mockDoctrine->shouldReceive('persist')
->andReturn(true);
$mockDoctrine->shouldReceive('flush')
->andReturn(true);
return $mockDoctrine;
}
These are my repositories mock
public function getRepositoryAMock()
{
$repository = \Mockery::mock('MyARepository');
$repository->shouldReceive('findBy')
->with(['paramA' => 1, 'paramB' => 1])
->andReturn($this->getMockA());
return $repository;
}
public function getRepositoryBMock()
{
$repository = \Mockery::mock('MyBRepository');
$repository->shouldReceive('findById')
->with(1)
->andReturn($this->getMockA());
return $repository;
}
public function getRepositoryCMock()
{
$repository = \Mockery::mock('MyCRepository');
$repository->shouldReceive('findOneBy')
->with(['paramA' => 1, 'paramB' => 1])
->andReturn($this->getMockA());
return $repository;
}
This is where in fact i set the return of my mock
public function getMockA()
{
$obj = new MyClass();
$reflection = new \ReflectionClass($obj);
$id = $reflection->getProperty('id');
$id->setAccessible(true);
$id->setValue($obj, 1);
$obj
->setLogin('foo')
->setPassword('bar')
->setCode(1);
return $obj;
}
And then i receive an error like this:
1) MyClassTest::testService
BadMethodCallException: Method Mockery_2_ClassBRepository::findOneBy() does not exist on this mock object
Assuming that i have 3 methods with repositories being called in testService() method, the method that mockery is not finding is in the third one, but mockery thinks it is in the second, so obviously he won't find, because in the second one, does not exist the "findOneBy()" doctrine method just in the third.
How can i solve this ?
You should be able to use mockery's with().
For example:
$mockDoctrine
->shouldReceive('getRepository')
->with('MyAReposiotry')->once()
->andReturn($this->getRepositoryAMock());
And like that for every repository (with different value in with).
But I would rather inject repositories in that service instead of getting it from entity manager inside service. It is way better for testing. Take a look at this blog post.
Thanks to Mr Ivan,
Now my method is working.
In my real class, i was getting the repository with the entity class,
like:
Doctrine::getRepository('MyEntityClassA')
->findBy(['paramA' => 1, 'paramB' => 1]);
so i changed my test method to use the "with" passing the entity class:
public function testService()
{
$mockDoctrine = $this->getMockDoctrine();
$mockDoctrine->shouldReceive('getRepository')
->with('MyEntityClassA')->once()
->andReturn($this->getRepositoryAMock());
$mockDoctrine->shouldReceive('getRepository')->once()
->with('MyEntityClassB')->once()
->andReturn($this->getRepositoryBMock());
$mockDoctrine->shouldReceive('getRepository')->once()
->with('MyEntityClassC')->once()
->andReturn($this->getRepositoryCMock());
//here is where i hit my test
$products = $this->service->fire(1, 1);
$this->assertInstanceOf('Illuminate\Support\Collection', $products);
foreach ($products as $v) {
$this->assertInstanceOf('Illuminate\Support\Collection', $v);
}
}
I'm using Mockery in my Laravel project to mock the User Eloquent model and test a route.
This is how I test the /api/user/activate route:
<?php
use Illuminate\Support\Facades\Session;
class ActivateTest extends TestCase
{
private $userMock;
public function setUp()
{
parent::setUp();
$this->userMock = Mockery::mock('App\User');
Session::start();
}
public function tearDown()
{
Mockery::close();
}
public function testActivate()
{
$this->userMock->shouldReceive('where->first')->once()->andReturn('test');
$this->userMock->shouldReceive('activate')->once();
$response = $this->call('POST', '/api/user/activate', [
'activationToken' => '838jfjnvu83u3',
'_token' => csrf_token()
]);
// This will be displayed in the PHPunit output
print_r($response->getContent());
$this->assertResponseStatus(200);
}
}
The problem I'm having is that the andReturn('test') doesn't seem to work. The PHPunit result is:
F{"error":{"message":null,"statusCode":404}}
Time: 276 ms, Memory: 15.50Mb
There was 1 failure:
1) ActivateTest::testActivate
Failed asserting that 404 matches expected 200.
This is the content of the activate() in the UserController:
public function activate(Request $request)
{
$activation = $request->input();
$user = $this->user->where('activationToken', $activation['activationToken'])->first();
if(!$user) return $this->respondNotFound($user);
try
{
$user->activate($activation['password']);
}
catch(ModelException $e)
{
return $this->respondInternalError($e->errorMessages());
};
return $this->respondCreated('Account activated.');
}
The problem is that $user in the controller is null because the mock is not returning test (in that case the condition would evaluate to true and I wouldn't get a 404 response).
Edit:
I also tried using PHPunit mocking but it wasn't successful:
$this->userMock = $this->getMockBuilder('App\User')->setMethods(['where', 'first', 'activate'])->getMock();
$this->userMock->expects($this->once())->method('where')->willReturn($this->userMock);
$this->userMock->expects($this->once())->method('first')->willReturn('test');
$this->userMock->expects($this->once())->method('activate');
It's not enough to mock an object. You need to get that mocked object to be injected into the class which contains that activate() function.
You can do that in your setUp() function as well. Try adding this...
$this->app->instance('App/User', $this->userMock);
That will tell Laravel when you want to inject an instance of App/User, to inject the mock object you just created instead.
The issue was caused by ->first() since it's not a method existing neither on the Eloquent or User classes.
To solve it I created a new UserRepository and injected it as a dependency in the controller constructor.
class UserRepository implements UserRepositoryInterface
{
/**
* #var User
*/
protected $user;
/**
* #param User $user
*/
public function __construct(User $user)
{
$this->user = $user;
}
/**
* #param $activationToken
* #return mixed
*/
public function whereActivationToken($activationToken)
{
return $this->user->where('activationToken', $activationToken)->first();
}
}
Injection in the UserController:
public function __construct(UserRepository $userRepository)
{
$this->userRepository = $userRepository;
}
And this is how the test PostActivateTest class looks like now:
use Illuminate\Support\Facades\Session;
class PostActivateTest extends TestCase
{
private $user;
private $userRepositoryMock;
public function setUp()
{
parent::setUp();
$this->user = Mockery::mock('App\User');
$this->userRepositoryMock = Mockery::mock('Repository\Database\UserRepository');
$this->app->instance('App\User', $this->user);
$this->app->instance('Bloom\BloomCRM\Repository\Database\UserRepository', $this->userRepositoryMock);
Session::start();
}
public function tearDown()
{
Mockery::close();
}
public function testActivate()
{
$this->userRepositoryMock->shouldReceive('whereActivationToken')->once()->andReturn($this->user);
$this->user->shouldReceive('activate')->once();
$this->call('POST', '/api/user/activate', [
'activationToken' => '838jfjnvu83u3',
'password' => 'test',
'_token' => csrf_token()
]);
$this->assertResponseStatus(201);
}
}
I keep fumbling over this - how do I mock a model that extends form Eloquent in Laravel 4 for my unit test?
I keep getting the following error w/ my current way
ErrorException: Trying to get property of non-object
Example
use \Repository\Text\EloquentText;
use \Faker\Factory as Faker;
class EloquentTextTest extends TestCase {
public function setUp()
{
parent::setUp();
$stub = $this->getMock('Text');
$stub->expects($this->any())->method('save');
$this->_fixture = new EloquentText($stub);
}
/**
* #test
*/
public function createShouldCreateNewTextEntry()
{
$faker = Faker::Create();
$data = [
'title' => $faker->sentence,
'content' => $faker->text,
'level_id' => $faker->randomDigit,
'is_public' => $faker->numberBetween(0, 1),
'is_visible' => $faker->numberBetween(0, 1),
];
$text = $this->_fixture->create($data);
$this->assertEquals($data['title'], $text->title);
$this->assertEquals($data['content'], $text->content);
$this->assertEquals($data['level_id'], $text->level_id);
$this->assertEquals($data['is_public'], $text->is_public);
$this->assertEquals($data['is_visible'], $text->is_visible);
return $text;
}
I am having problems with testing sometimes. The test is executed, i can even get the results of the action but the test doesn't show its green or red message telling me if the test run successfully or not. (28/28 test methods complete: 28 passes, 0 fails, 95 assertions and 0 exceptions...), the time, peak memory... etc.
For example. Only if i try to execute this test it crashes:
public function testGetHotVideo() {
$result = $this->testAction("/posts/getHotVideo/");
$this->assertEquals($result, 'GoG_Tv5G17M');
}
It calls this method:
public function getHotVideo(){
$video = $this->Post->getHotVideo();
return $video[0][0]['video'];
}
And it returns the video string correctly. So it can even be printed on the test variable $result.
What's going on here?
UPDATE
I have also notice it occurs the same when i call a method which redirects to some view.
In this case, with the default delete created with Cake Bake:
public function delete($id = null){
$this->Comment->id = $id;
if (!$this->Comment->exists()) {
throw new NotFoundException(__('Invalid comment'));
}
if ($this->Comment->delete()) {
$this->Session->setFlash(__('Comment deleted'));
return $this->redirect(array('controller' => 'posts', 'action' => 'index'));
}
$this->Session->setFlash(__('Comment was not deleted'));
return $this->redirect(array('controller' => 'posts', 'action' => 'index'));
}
Is this SimpleTest (cakephp <= 1.3) or PHPUnit (>1.3)?
In case its SimpleTest you have to handle the redirects in your controller tests (read Mark Storys article on controller testing). Basically you build a test class which inherits from the controller class you are testing and than you overwrite the redirect and some other methods (here some from the article):
class TestPostsController extends PostsController {
...
function redirect($url, $status = null, $exit = true) {
$this->redirectUrl = $url;
}
...
}
than you use this modified controller in your test case.
With CakePHP 2 and PHPUnit I even had some issues with calling the header() function in some of the layouts, which broke the html structure and the css of the test-sides.