I have spent a day trying debugging and trying to understand what is going wrong but...
So here is my code:
<?php
namespace RememberCalories\Rest;
interface MyJsonResponseInterface
{
public function getResponse();
}
And here is the class which I want to inject:
<?php
namespace RememberCalories\Rest;
class MyJsonResponse implements MyJsonResponseInterface
{
protected $success;
protected $responseCode;
protected $responseMsg;
protected $data;
public function __construct($data, $responseCode=0, $responseMsg='')
{
$this->data = $data;
$this->responseCode = $responseCode;
$this->responseMsg = $responseMsg;
if ( $this->responseCode === 0 ) {
$this->success = true;
}
}
...
Binding:
\App::bind('MyJsonResponseInterface', function($app, $parameters) {
$obj = new \RememberCalories\Rest\MyJsonResponse(null);
// var_dump($obj);
// die();
return $obj;
});
And at last the controller:
<?php
use \RememberCalories\MainMenu\MainMenu;
use \RememberCalories\Repository\TargetEloquentRepository as TargetRepository;
use \RememberCalories\Rest\MyJsonResponseInterface;
//use \RememberCalories\Rest\MyJsonResponse;
class BaseController extends Controller
{
protected $viewVars;
protected $mainMenu;
//Dependency injection classes
protected $target;
protected $myJsonResponse;
public function __construct(TargetRepository $target, MyJsonResponseInterface $myJsonResponse )
{
$this->beforeFilter('accessFilter');
$this->target = $target;
//$this->myJsonResponse = $myJsonResponse;
$this->mainMenu = (new MainMenu())->build();
$this->prepareViewVariables();
}
So the problem is with the second parameter of BaseController: MyJsonResponseInterface. The first is injected without problems but this one I get an error:
Illuminate \ Container \ BindingResolutionException
Target [RememberCalories\Rest\MyJsonResponseInterface] is not
instantiable.
It seems that the Closure in \App::bind('MyJsonResponseInterface' ...) is not called.
I have moved it to service provider with the same result.
But at the same if to call manually \App::make('MyJsonResponseInterface') everything is created ideally.
Please advise what way to investigate.
You need to App::bind the full namespace - so in your example, App::bind('RememberCalories\Rest\MyJsonResponseInterface').
Related
PostController's store method which calls the service class and service class calls the third party api i.e. line. while storing a post. i want to write testcase if the notify field is true then it sends notification to the user's through line if not then return with error message. i am not getting any idea how to perform this test. here is the code for PostController.php
private Post $post;
private PostService $service;
public function __construct(
PostService $service,
Post $post
) {
$this->service = $service;
$this->post = $post;
}
public function store(PostRequest $request): RedirectResponse
{
$post = $this->service->createPost($request->validated());
if ($request->notify) {
$message = 'lorem ipsum';
$this->service->lineSendToGroup($request->category_big_id, $message);
}
return redirect()->to('/posts')->with('success_message', 'Post created successfully.');
}
PostService.php
use App\Library\Line;
use App\Models\CategoryBig;
class PostService
{
private CategoryBig $categoryBig;
public function __construct(
CategoryBig $categoryBig,
) {
$this->categoryBig = $categoryBig;
}
public function lineSendToGroup(int $categoryBigId, string $message): void
{
$catB = $this->findOrFailCategoryBig($categoryBigId);
Line::send(
$catB->line_message_channel_secret,
$catB->line_message_channel_access_token,
$catB->line_group_id,
$message
);
}
public function findOrFailCategoryBig(int $categoryBigId): CategoryBig
{
return $this->categoryBig->whereId($categoryBigId)->firstOrFail();
}
public function createPost(array $createData): Post
{
$createData += [$data];
return $this->greeting->create($createData);
}
Line.php
namespace App\Library;
use Illuminate\Support\Carbon;
use LINE\LINEBot;
use LINE\LINEBot\HTTPClient\CurlHTTPClient;
use LINE\LINEBot\Event\MessageEvent\TextMessage;
use LINE\LINEBot\MessageBuilder\TextMessageBuilder;
use Log;
class Line
{
public static function send($channel_secret, $access_token, $line_user_id, $message)
{
$http_client = new CurlHTTPClient($access_token);
$bot = new LINEBot($http_client, ['channelSecret' => $channel_secret]);
$textMessageBuilder = new TextMessageBuilder($message);
$response = $bot->pushMessage($line_user_id, $textMessageBuilder);
if ($response->isSucceeded()) {
return true;
} else {
return false;
}
}
}
Test
public function test_notification_when_LINE_notification_is_specified()
{
}
Step 1)
You can write a unit test for Line class.
For ease of use, you can refactor this class and use Laravel Http client instead. Create different stubs for each line's response (e.g 200) then use Laravel testing helpers to mock resposne.
Step 2)
Write a feature test for your controller. In this test you don't care about internal functionality of Line class and just want to make sure method of this class is called with proper arguments.
You can use mockery to achieve that.
Migrate to non-static method on Line class. You can't mock static methods.
For happy path test:
$mock = Mockery::mock(\App\Library\Line::class);
$mock->shouldReceive('send')
->once()
->with($secret, $token, $groupId, $message)
->andReturn(true);
// of course mocking object doesn't do much alone and you need to bind and resolve it.
$this->app->instance(\App\Library\Line::class, $mock);
Then resolve it inside your PostService#lineSendToGroup
resolve(Line::class, [
'token' => $token,
// ... other args
])
For opposite scenario use shouldNotReceive('send') instead.
I have a api trait that connects to an external endpoint. I want to use this trait in a class called ProductClass. The trait is in the same folder as the class, but I get a error is I add use ApiTrait in the class. Error says it cannot find the trait, So if I include the trait file at the top of the class file, I get this error, cannot find ApiTrait in
ProductClass\ApiTrait.
If i pass the trait into the constructor I get an error from my index page when I call the ProductClass because I am not passing in the trait. I dont want to pass any params to the constructor just the string top append to the .env endpoint. any clues greatly appreciated
heres my ApiTrait code
<?php
namespace ApiTrait;
require './vendor/autoload.php';
use GuzzleHttp\Client;
trait ApiTrait
{
protected $url;
protected $client;
public function __construct()
{
$this->url = getenv('API_URL');
$this->client = new Client();
}
private function getResponse(String $uri = null)
{
$full_path = $this->url;
$full_path .=$uri;
try {
$response = $this->client->get($full_path);
}
catch (GuzzleHttp\Exception\ClientException $e) {
$response = $e->getResponse();
}
return json_decode($response->getBody()->getContents(), true);
}
public function getAPIData($uri)
{
return $this->getResponse($uri);
}
}
this is my ProductClass code
<?php
namespace ProductClass;
include_once("ApiTrait.php");
use DataInterface\DataInterface;
class Product implements DataInterface
{
use ApiTrait\ApiTrait
private $api;
public function __construct(ApiTrait\ApiTrait $apiTrait) {
$this->api = $apiTrait;
}
private function getResponse($append, $try) {
$urlAppend = $append;
$good_data = false;
do{
try{
$result = $this->api->getAPIData($urlAppend);
//check data to see if valid
if(!array_key_exists( "error",$result)){
$good_data = true;
return $result;
}
}
catch(Exception $e){
//call api upto 10 times
if($try < 10) {
sleep(1);
getData($append, $try++);
} else { //return a connection error
$api_error['error']='unable to connect to api';
return $api_error;
}
}
} while($good_data === false);
}
public function getData($append, $try = 0)
{
return $this->getResponse($append, $try);
}
}
If you're using an autloader, you shouldn't ever need this:
include_once("ApiTrait.php");
You've got your trait defined in the ApiTrait namespace:
namespace ApiTrait;
trait ApiTrait { ... }
I.e., the trait's full path is \ApiTrait\ApiTrait. If you're using the trait in a namespace other than the one it's defined, then you need to anchor from the root namespace when referring to it, by preceding it with a backslash:
namespace ProductClass;
class Product implements DataInterface
{
use \ApiTrait\ApiTrait;
Otherwise, if you do use ApiTrait\ApiTrait; without the leading backslash, then PHP thinks you're referring to the current namespace, which is ProductClass, yielding \ProductClass\ApiTrait\ApiTrait -- which doesn't exist, hence your error.
You could also do it this way with class aliases:
namespace ProductClass;
use ApiTrait\ApiTrait;
class Product implements DataInterface
{
use ApiTrait;
Also, it looks like you're just putting every class it its own namespace. Don't do that. Use namespaces to group common items, for example, something like this:
namespace Traits;
trait Api { ... }
namespace Traits;
trait Foo { ... }
namespace Traits;
trait Bar { ... }
namespace App;
class Product {
use \Traits\Api;
use \Traits\Foo;
use \Traits\Bar;
}
I'm trying to test my Category class. I'm using Mockery::mock() method, with 'overload:' prefix and makePartial() method.
When running test I have this error:
Mockery\Exception\BadMethodCallException : Method App\Models\Category::getDynamicFieldsForDocument() does not exist on this mock object
Here is my code:
namespace App\Models;
class Category extends DictionaryBase{
//some methods
public function getDynamicFieldsForDocument()
{
$data = [];
$parents = [];
$allParents = $this->getParents($this->id, $parents);
foreach ($allParents as $parentId) {
$parent = Category::find($parentId);
$fields = $parent->dynamicFields();
foreach ($fields as $field) {
$data[$field['name']] = $field;
}
}
return $data;
}
}
TestCase:
namespace Tests\Unit;
use App\Models\Category;
use Tests\TestCase;
class CategoryModelTest extends TestCase{
//some methods
/**
* #runInSeparateProcess
* #preserveGlobalState disabled
*/
public function testGetDynamicFieldsForDocument()
{
$mockCategory = \Mockery::mock('overload:'.Category::class)->makePartial();
$preparedDynamicFields = $this->prepareDynamicFields();
$preparedCategories = $this->prepareCategories();
$mockCategory->shouldReceive('find')->andReturn($preparedCategories[0], $preparedCategories[1], $preparedCategories[2]);
$mockCategory->shouldReceive('getParents')->andReturn(['1a2b', '3c4d', '5e6f']);
$mockCategory->shouldReceive('dynamicFields')->andReturn(null, $preparedDynamicFields[0], $preparedDynamicFields[1]);
$response = $mockCategory->getDynamicFieldsForDocument();
dd($response);
}
}
I have no idea why i still have error. I think when ->makePartial() method is called it should mock only methods, which are called by ->shouldReceive()
EDIT:
Now I'm making mock instance without :overload, and mocking 'find' method in this way:
`$mockCategory->shouldReceive('find')->andReturn($preparedCategories[0], $preparedCategories[1], $preparedCategories[2]);`
My find method looks like this:
public static function find($id) {
return $id ? self::list(config(static::IDENT.'.fields'), (new Filter('and'))->add('id', $id, '')->toArray(),[],1,1)[0] ?? null : null;
}
And this is my error:
Error : Wrong parameters for App\Exceptions\ApiException([string
$message [, long $code [, Throwable $previous = NULL]]])
It's because list method call API so it looks like this method is called without mock.
I know that i can't mock static method, but earlier when I used :overload it was possible. What's now?
Delete :overload and just define your mock as:
$mockCategory = \Mockery::mock(Category::class)->makePartial()
Example
Model:
namespace App\Models;
class Foobar extends BaseModel
{
public function foonction()
{
Foobar::find();
return '1';
}
}
Test:
namespace Tests;
use Evalua\Heva\Models\Foobar;
class FoobarTest extends TestCase
{
public function testFoobar()
{
$fooMock = \Mockery::mock('overload:'.Foobar::class)->makePartial();
$fooMock->shouldReceive('find')->once();
$fooMock->foonction();
}
}
Fails with:
Mockery\Exception\BadMethodCallException: Method Evalua\Heva\Models\Foobar::foonction() does not exist on this mock object
Without the :overload the test pass.
The explanation should be based on what's written in the documentation about overload:
Prefixing the valid name of a class (which is NOT currently loaded) with “overload:” will generate an alias mock (as with “alias:”) except that created new instances of that class will import any expectations set on the origin mock ($mock). The origin mock is never verified since it’s used an expectation store for new instances. For this purpose we use the term “instance mock”
I am trying to write a unit test for a function that immediately loads an object from a different class that uses the input to the function as a parameter. I am new to php unit testing and couldn't find anything that address my particular problem. A few leads that I had that led to no avail was using an injector, and trying to us a reflection.
The code I am trying to write a unit test for is:
public static function isUseful($item) {
$objPromo = MyPromoCodes::Load($item->SavedSku);
if (!is_null($objPromo)
&& ($objPromo->PromoType == MyPromoCodes::Interesting_Promo_Type)) {
return true;
}
return false;
}
My attempt at mocking this out:
public function testIsUseful() {
$injector = $this->getMockBuilder('MyPromoCodes')
->setMethods(array('Load'))
->getMock();
$objPromo = $this->getMock('MyPromoCodes');
$objPromo->PromoType = 'very interesting promo type';
$injector->set($objPromo, 'MyPromoCodes');
$lineItem1 = $this->getDBMock('LineItem');
$this->assertTrue(MyClass::isUseful($lineItem1));
}
however this doesn't work because there is no set method for this object....
Not sure what else to try, any help would be appreciated.
I made the library that makes static classes mocking possible:
class MyClass {
public static $myPromoCodes = 'myPromoCodes';
public static function isUseful($item) {
$objPromo = self::$MyPromoCodes::Load($item->SavedSku);
if (!is_null($objPromo)
&& ($objPromo->PromoType == MyPromoCodes::Interesting_Promo_Type)) {
return true;
}
return false;
}
}
class MyClassTest extends \PHPUnit_Framework_TestCase
{
public function testSomething()
{
$myClass = Moka::stubClass('MyClass');
$myClass::$myPromoCodes = Moka::stubClass(null, ['::Load' => (object)[
'PromoType' => MyPromoCodes::Interesting_Promo_Type
]]);
$this->assertTrue($myClass::isUseful((object)['SavedSku' => 'SKU']);
$this->assertEquals([['SKU']], $myClass::$myPromoCodes->moka->report('::Load'));
}
}
To start with you cannot mock static method with PHPUnit. At least not with 4.x and 5.x.
I would suggest a DI approach like this:
class MyClass
{
private $promoCodesRepository;
public function __construct(MyPromoCodesRepository $promoCodesRepository)
{
$this->promoCodesRepository = $promoCodesRepository;
}
public function isUseful(MyItem $item)
{
$objPromo = $this->promoCodesRepository->Load($item->SavedSku);
// ...
}
}
Here you can easily mock the Load method.
Unfortunately the "static" approach creates a lot of issues during tests so it is better to avoid it whenever possible.
I try to extend the CheckfrontAPI class with my new class.
In my case I use the Singleton pattern in order to load only one instance at a time of my class and I get that error
Fatal error: Declaration of CheckFrontIntegrator::store() must be compatible with that of CheckfrontAPI::store() in /home/my_web_site/public_html/wp-content/plugins/checkfront/class/Checkfront_Integration.php on line 83
Any idea on how to solve that issue ?
Here is the CheckfrontAPI source code : https://github.com/Checkfront/PHP-SDK/blob/master/lib/CheckfrontAPI.php
And here is my class that extends that class:
<?php
class CheckFrontIntegrator extends CheckfrontAPI
{
private static $instance = null;
public $tmp_file = '.checkfront_oauth';
final protected function store($data = array())
{
$tmp_file = sys_get_temp_dir() . DIRECTORY_SEPARATOR. $this->tmp_file;
if(count($data))
{
file_put_contents(
$tmp_file,
json_encode(
$data,
true
)
);
}
elseif(is_file($tmp_file))
{
$data = json_decode(
trim(
file_get_contents(
$tmp_file
)
),
true
);
}
return $data;
}
public function session($session_id, $data = array())
{
$_SESSION['checkfront']['session_id'] = $session_id;
}
public static function instance($data)
{
if(!isset(self::$instance))
{
self::$instance = new CheckFrontIntegrator($data);
}
return self::$instance;
}
public function __construct($data)
{
if(session_id() == '')
{
session_start();
}
parent::__construct($data, session_id());
}
}
?>
And I initiate the new instance of that class like that:
$this->checkfront_integrator = CheckFrontIntegrator::instance($args);
where args are all the important information needit by the class to initiate a new object
AFTER EDIT
I have change my method store from:
final protected function store($data = array())
....
to
protected function store($data)
....
and the problem still occure :(
CheckfrontAPI is an abstract class? in this case your CheckFrontIntegrator::store() arguments count must be identical to original declaration
EDIT
I see on github
abstract protected function store($data);
your override must be:
protected function store($data) {
}
You are extending CheckfrontAPI. CheckfrontAPI has a method store(). If you override that method you must do it properly.
Post the code of CheckfrontAPI and your class Checkfront_Integration: when can understand what's the problem.
When you want to extent the functionality of an existing class by writing your own class and the class you are extending is is an abstract one, you'll need to make sure that the function calls are compatible.
What does this mean?
If the class you are extending has this function call for example :
function walk($direction, $speed = null);
Then you will have to honor the function signature in your implementation - that means you'll still have to have to pass two function arguments in your version.
You will not be able to alter is to be like this :
function walk($direction, $speed, $clothing);