I'm trying to write my first unit test in cakePHP.
I would like to try it out with the following action:
public function joinLobby($userId = null, $lobbyId = null) {
// json webservice layout
$this->layout = 'ajax';
if (!$userId || !$lobbyId) {
throw new CakeException(__('Paramter(s) missing'));
}
if (!$this->Lobby->exists($lobbyId)) {
throw new CakeException(__('Invalid lobby supplied'));
}
if (!$this->User->exists($userId)) {
throw new CakeException(__('Invalid user supplied'));
}
$this->__addUsersToLobby(array($userId), $lobbyId);
}
And my test case goes like this so far:
public function testShouldNotAddNonExistentUserToLobby() {
$this->LobbiesController = $this->generate('Lobbies', array(
'methods' => array(
'joinLobby',
'__addUsersToLobby'
),
'models' => array(
'Lobby',
'User'
)
));
$this->LobbiesController->Lobby->expects($this->any())->method('exists')->will($this->returnValue(true));
$this->LobbiesController->User->expects($this->once())->method('exists')->will($this->returnValue(false));
$this->LobbiesController->expects($this->never())->method('__addUsersToLobby');
$this->testAction('Lobbies/joinLobby/1/1');
}
When I run my test, I get this:
UsersLobbiesControllerTest::testShouldNotAddNonExistentUserToLobby
Expectation failed for method name is equal to when invoked 1 time(s).
Method was expected to be called 1 times, actually called 0 times.
The only way I can get the test to pass is by changing both "$this->once()" to "$this->any()" or "$this->never()".
Any help??
Thanks in advance!
I've finally managed to make it work.
The problem was that I was mocking the same function I was testing, so it wasn't actually called. Therefore, the code that called the other functions wasn't executed.
Removing 'joinLobby' from the 'methods' array made the work.
Cheers
Related
I'm new to PHPUnit and wondering is it possible to write a test for which ignore specific method.
The code is like examine whether $data is Valid or not, and if it find irregular data, send message to slack with it.
My question is, is it possible to run a test without sending alert message, like ignore sendAlert function?
If possible, I want to know how to write it, If not, I want know why and how to make this code testable.
Thanks!!
example code )
public static function isValid($data) {
// some code here
if (Valid) {
return true;
} else {
// some code here to find irregular
if (irregular) {
self::sendAlert($data);
}
return false;
}
}
private static function sendAlert($data) {
// send alert to slack
Example_Model_Slack::post($slackMsg, $channel);
}
<?
class Example_Model_Slack
{
public static function post($text, $channel = '') {
// make $params from $text and $channel
// POST
$stream = [
'http' => [
'method' => 'POST',
'protocol_version' => 1.1,
'content' => http_build_query($params),
],
];
return file_get_contents(self::POST_URL, false, stream_context_create($stream));
}
}
Edit after the question edit
If your code is in a namespace (which should be, it's good practice), it's extremely easy:
Create a new function in a separate file that is only included by your UnitTest file. This file should have the same namespace as your code. In this example, Example_Model_Slack is in the namespace Foobar\Models.
<?php
namespace Foobar\Models;
function file_get_contents(string $filename, bool $use_include_path = false, resource $context = ?)
{
return 'Whatever you want';
}
When you call a function, the code looks for it:
In the specifically used functions.
In the same namespace.
In the built-in functions.
Therefore, your code will use the built-in file_get_contents (namely \file_get_contents), but your test will use the one in the same namespace (namely \Foobar\Models\file_get_contents).
Original answer
The easiest would be to actually call sendAlert, but to mock the call to its content. As you didn't provide the code of that method, I can't be more precise, juste browse through the doc and figure it out by yourself or, alternatively, show us the code.
For a theorectical and general answer: your sendAlert method probably uses one that is provided by an external vendor, let's say \SlackApi\Slack::send($message). In that case, you could mock the provided \SlackApi\Slack class to replace the send method with one that doesn't actually send anything but still returns the expected data.
I have a model Driver which have columns: name, branch, status_id, etc..Updating is actually fine and working, my problem is how can I return the updated one?
Here's what I tried so far, but it returns a boolean, resulting of returning an error in my console:
The Response content must be a string or object implementing __toString(), "boolean" given.
public function updateStatus(Driver $driver)
{
return $driver->update($this->validateStatus());
}
public function validateStatus()
{
return $this->validate(request(), [
'status_id' => 'required|min:1|max:3'
]);
}
I expect it should return the all the columns of a driver.
I've been to this link but it doesn't helped. Someone knows how to do this?
You can use tap() helper, which will return updated object after the update like so:
return tap($driver)->update($this->validateStatus());
More on that here: Tap helper
return as object instead of boolean type
public function updateStatus(Driver $driver)
{
$driver->update($this->validateStatus());
return $driver;// first way
// return tap($driver)->update($this->validateStatus()); //second way
}
public function validateStatus()
{
return $this->validate(request(), [
'status_id' => 'required|min:1|max:3'
]);
}
I think no need any model helper for that
in controller you can do like this
$driver = Driver::find(1);
$driver->name = "expmale";
$driver->save();
return $driver;
or other way
$driver = Driver::find(1);
$driver->update([
'name'=> "expmale"
]);
return $driver;
It's work for me
$notify = tap(Model::where('id',$params['id'])->select($fields))->update([
'status' => 1
])->first();
I know that there's already an answer for this, but ideally, you don't want to use the update method. It's just a model helper method that doesn't really add much. Internally it does what I have included below, except it returns the result of save().
You'd want to do something like this:
if ($driver->fill($this->validateStatus)->save()) {
return $driver;
}
throw new \RuntimeException('Update failed, perhaps put something else here);
The problem you're going to have with the accepted answer (and most of the others) is that you return the model without ever checking that it was actually updated, so you're going to run into issues down the line when it's not updating the actual database even though it's reporting that it is.
I have the following 2 rules:
'blog/<action>' => 'blog/default/<action>',
'blog/<slug:[0-9a-zA-Z\-.]+>' => 'blog/default/view',
Also I have the following actions:
public function actionCheckSlug($slug) {
}
public function actionCreate() {
}
public function actionView($slug) {
return $this->render("view");
}
When I try to access this URL for example (action URL):
/blog/check-slug?slug=test
It's working without any problems but when I try to access this URL for example (Slug URL):
/blog/test-test-test
I will get an error:
yii\base\InvalidRouteException: Unable to resolve the request: blog/default/test-test-test
Because the fist rules is being parsed instead of the second one.
I tried to reverse them for example but it didn't work (always one is not working), also tried others scenarios but no success
Any idea how to make it works?
i would suggest first of all not using the same url convention for both actions
'blog/<action>' => 'blog/default/<action>',
'blog/<slug:[0-9a-zA-Z\-.]+>' => 'blog/default/view',
could easily become
'blog/<action>' => 'blog/default/<action>',
'post/<slug:[0-9a-zA-Z\-.]+>' => 'blog/default/view',
or use 'blog/page-<slug:[0-9a-zA-Z\-.]+>', blog/post/ .. or really just any convention that doesn't clash with your existing structure
if that's not something just you wanna do, or cant? in your app, you can just use the slug to check for existing app structure.
public function view($slug){
$model = $this->findBySlug($slug);
return $this->render('view', ['model' => $model]);
}
private function findBySlug($slug){
if ($this->hasMethod('action' . Inflector::classify($slug))
// this should prevent recursion
&& $slug != $this->action->id){
$this->runAction($slug);
return null;
}
return Post::find()->where(['slug' => $slug])
}
note: this is just an example of how to (or how not to?). don't run my bad, untested code in any production environment
I have an application with a third party plugin that handles ACL management, works fine, but I am having some issues with a isAuthorized function. The user is being passed into the function and when I debug the variable I can see all of the correct information in the dataset, but when the script executes the find query it returns empty. Here is the thing, the model I am executing the query on is apart of the plugin I am using. So I am executing a find within my application on a model that lies within my plugin. To me it isn't ideal, nonetheless I am working with a plugin that wasn't built application specific. In order to execute the find the Author added
App::uses('User', 'AuthAcl.Model');
so the find could be possible. Now that said, I tweaked one thing in the conditions part of the statement because I was getting Column 'id' in field list is ambiguous error from SQL. I started to get that error because I added some relationships to the application.
I say all that to say this - since I am loading the model via App Uses shouldn't I be able to execute a find like so
$this->User->Find('First');
I have tried that and it says I'm executing on a non object. Here is the code for that script. I need input. When I execute my script without debugging it locks me out the application, and by reading the code and debugging it I think this happens because the find is returning empty. And on another note - please bear with me - I didn't write this code, and I am in the phase of learning to understand other developers code. I am using CAKEPHP 2.3 Thanks guys!
public function isAuthorized($user = null) {
App::uses('User', 'AuthAcl.Model');
App::uses('Group', 'AuthAcl.Model');
$authFlag = false;
$this->set('login_user',$user);
$userModel = new User();
$group = new Group();
//die(debug($user));
$rs = $this->$userModel->find('first',array('conditions'=>array('user.id' => $user['id'])));
die(debug($rs));
$action = 'controllers';
if (!empty($this->plugin)){
$action .= '/'.$this->plugin;
}
$action .= '/'.$this->name;
$action .= '/'.$this->action;
if (!empty($rs['Group'])){
foreach ($rs['Group'] as $group){
$authFlag = $this->Acl->check(array('Group' => array('id' => $group['id'])), $action);
if ($authFlag == true){
break;
}
}
}
if ($authFlag == false && !empty($user)){
$authFlag = $this->Acl->check(array('User' => array('id' => $user['id'])), $action);
//die(debug($authFlag));
}
if ($authFlag == false && !empty($user)){
$this->redirect(array('controller' => 'accessDenied', 'action' => 'index','plugin' =>'auth_acl'));
}
if (!empty($user)){
$user = $userModel->find('first',array('conditions' => array('user.id' => $user['id'])));
$this->Session->write('auth_user',$user);
$this->request->data['auth_plugin'] = $this->plugin;
$this->request->data['auth_controller'] = $this->name;
$this->request->data['auth_action'] = $this->action;
}
return $authFlag;
}
}
I have a class that I am writing and I have a method that I would like to run once per initiation of the class. Normally this would go in the construct method, but I only need it to run when I call certain methods, not all.
How would you all recommend I accomplish this?
Create a private property $methodHasBeenRun which has a defualt value of FALSE, and set it to TRUE in the method. At the start of the method, do:
if ($this->methodHasBeenRun) return;
$this->methodHasBeenRun = TRUE;
You didn't specify exactly why you only want to run a given method once when certain methods are called, but I am going to make a guess that you're loading or initializing something (perhaps data that comes from a DB), and you don't need to waste cycles each time.
#DaveRandom provided a great answer that will work for sure. Here is another way you can do it:
class foo {
protected function loadOnce() {
// This will be initialied only once to NULL
static $cache = NULL;
// If the data === NULL, load it
if($cache === NULL) {
echo "loading data...\n";
$cache = array(
'key1' => 'key1 data',
'key2' => 'key2 data',
'key3' => 'key3 data'
);
}
// Return the data
return $cache;
}
// Use the data given a key
public function bar($key) {
$data = $this->loadOnce();
echo $data[$key] . "\n";
}
}
$obj = new foo();
// Notice "loading data" only prints one time
$obj->bar('key1');
$obj->bar('key2');
$obj->bar('key3');
The reason this works is that you declare your cache variable as static. There are several different ways to do this as well. You could make that a member variable of the class, etc.
I would recommend this version
class example {
function __construct($run_magic = false) {
if($run_magic == true) {
//Run your method which you want to call at initializing
}
//Your normale code
}
}
so if you do not want to run it create the class like
new example();
if you want
new example(true);