I am looking for the best way to test a call in an API I have created.
My test is:
/**
* #test
*/
public function it_can_return_a_block()
{
$block = new Block();
$block->type = "MyComp\MyOtherPackage\Widget";
$block->save();
$response = $this->call('GET', 'http://api.mysite.com/blocks?id=1');
$response->assertExactJson([
"id" => 1,
"type" => "MyComp\MyOtherPackage\Widget",
"component" => "widget",
"name" => "My Widget"
]);
}
The Block model for this uses the type attribute to instantiate a MyComp\MyOtherPackage\Widget instance which is extended from another class: MyComp\MyPackage\BlockDefintion which is abstract:
public class Block
{
...
private $definition;
public function getDefinitionAttribute()
{
if(!$this->definition)
{
$this->definition = new $this->type;
}
return $this->definition;
}
public function getNameAttribute()
{
return $this->definition->name;
}
public function getComponentAttribute()
{
return $this->definition->component;
}
...
}
This test works fine if MyComp\MyOtherPackage\Widget is real and can be instantiated but these clases will be in a different package so I want to decouple the relationship in my test.
Is there a way to create some kind of dummy class to use here? I was thinking Mockery but not sure how I can use this in this instance
Related
Here is a service that implements interface
interface Rating
{
function create(User $user, User $rateduser, Rating $rating, Order $order);
}
The controller acceepts the request object:
{"rating": [{"rateid": 1, "rate": 4}], "userid": 1}
When I try to pass whole data into service:
public function store(RateRequest $request)
{
$rating = RatingData::from(
[
'rateid' => $request->get('rating.rateid'),
'rate' => $request->get('rating.rate'),
]
);
$user = Auth()::user();
$rateduser = User::find($request->id);
$order = new Order;
$this->rating->create($user, $rateduser, $rating, $order);
}
How is better pass parametres into funciton using ideniticators $userid, $rateUserId, $orderId or concrete models? Or left it such as now?
interface Rating
{
function create(int $userid, int $rateUserId, Rating $rating, int $order);
}
The controller looks dirty in my case.
Another problem what if tomorrow my model will be changed from eloquent to another? Then I have to change service methods
I can suggest the following approach if you want to keep your controllers clean. (I do not claim that it is the only correct one)
Dto:
class RatingDto extends DataTransferObject
{
public int $rateId;
public int $rate;
}
class CreateRatingDto extends DataTransferObject
{
public int $userId;
public int $rateUserId;
/** #var RatingDto[] */
#[CastWith(ArrayCaster::class, itemType: RatingDto::class)]
public array $ratings;
}
Request:
class CreateRatingRequest extends FormRequest
{
public function rules(): array
{
return [
'ratedUserId' => 'required|integer|exists:users,id',
'ratings' => 'required|array',
'ratings.*.rateId' => 'required|integer',
'ratings.*.rate' => 'required|integer|min:1|max:5',
];
}
public function getDto(): CreateRatingDto
{
return new CreateRatingDto([
'userId' => Auth::user()->id,
'ratedUserId' => $this->input('ratedUserId'),
'ratings' => $this->input('ratings'),
]);
}
}
Interface:
interface RatingServiceInterfate
{
public function create(CreateRatingDto $dto): RatingModel;
}
Controller:
class RatingController extends Controller
{
private RatingServiceInterfate $ratingService;
public function __construct(RatingServiceInterfate $ratingService)
{
$this->ratingService = $ratingService;
}
public function store(CreateRatingRequest $request): JsonResource
{
$rating = $this->ratingService->create($request->getDto());
return new RatingResource($rating);
}
}
Also, this implementation does not bind your service to models. At any time you can write a new interface implementation and substitute it through ServiceProvider. (if for example in another part of the application you need to save data to another database not compatible with eloquent)
In Illuminate\Support\Facades\Facade abstract In method
protected static function resolveFacadeInstance($name)
{
if (is_object($name)) {
return $name;
}
if (isset(static::$resolvedInstance[$name])) {
return static::$resolvedInstance[$name];
}
return static::$resolvedInstance[$name] = static::$app[$name];
}
static::$app is an instance of Application. And static::$app[$name] that like access value of array, and i don't understand that, What technique here?
ex: static::$app['router'] it return instance of Router. Seem that get values of protected $instances in Illuminate\Container\Container
I think it like example ? but got FATAL ERROR Uncaught Error: Cannot use object of type Foo as array
class Foo
{
public $bar = 'barValue';
}
$foo = new Foo();
echo $foo['bar'];
If you check the API of Illuminate\Container\Container, you will notice that it implements ArrayAccess and consequently the following methods.
offsetExists()
offsetGet()
offsetSet()
offsetUnset()
ArrayAccess lets you access objects as arrays. Here's a very simplistic example of a Container.
<?php
class Container implements ArrayAccess {
private $items = array();
public function __construct() {
$this->items = [
'one' => 1,
'two' => 2,
'three' => 3,
];
}
public function offsetSet($offset, $value) {
if (is_null($offset)) {
$this->items[] = $value;
} else {
$this->items[$offset] = $value;
}
}
public function offsetExists($offset) {
return isset($this->items[$offset]);
}
public function offsetUnset($offset) {
unset($this->items[$offset]);
}
public function offsetGet($offset) {
return isset($this->items[$offset]) ? $this->items[$offset] : null;
}
}
$container = new Container();
echo $container['one']; // outputs 1
$container['four'] = 4; // adds 4 to $items.
echo $container['four']; // outputs 4
As you can see, you can access the Container object as an array since it implements ArrayAccess.
It also doesn't matter if the items property is not publicly accessible. In any case, the implementation of ArrayAccess means that it will allow us to retrieve those values as if they were in an array.
Hello I have a test case which will call a route and it will return some data if the session will set.
Here is my test case
class TestControllerTest extends TestCase
{
// ...
public function testResponseOfJson()
{
$response = $this->call('GET', 'profile/test');
$this->assertEmpty( !$response );
}
// ...
}
and here is my controller
Class TestController{
public function sendResponse(Request $request){
$this->data['user_id'] = $this->request->session()->get('userdata.userid');
if($this->data['userid']){
return data;
}
else{
return Failed;
}
}
}
My routes.php
Route::get('profile/test',['uses'=>'TestController#sendResponse']);
how can i set the session variable userdata.userid and get while doing unit testing.
Please check this page.
Laravel gives you the capability of using withSession chain method and other functions that will help you test where a session is required or needs to be manipulated in some way.
Example:
<?php
class ExampleTest extends TestCase
{
public function testApplication()
{
$response = $this->withSession(['foo' => 'bar'])
->get('/');
}
}
I am still used to MVC concept but i understand the basic concept of it.
I found this code on a "PHP" blog.
<?php
class Todo_Controller extends Base_Controller
{
public function action_list() {
$todos = Todo::all();
return View::make("list")
->with("todos", $todos);
}
public function action_view($id) {
$todo = Todo::where_id($id)->first();
return View::make("view")
->with("todo", $todo);
}
public function action_delete($id) {
$todo = Todo::where_id($id)->first();
$todo->delete();
return View::make("deleted");
}
public function action_new() {
return View::make("add");
}
public function action_add() {
$todo = new Todo();
$todo->title = Input::get("title");
$todo->description = Input::get("description");
$todo->save();
return View::make("success");
}
}
That is a controller but I notice action_list(), action_view() and action_delete() are running SQL but it is doing it in a controller.
Why is that? shouldn't that be in the model? Isn't the purpose of a model to do anything data related?
The reason why I am asking this is because I have seen a lot of laravel tutorials both paid and unpaid ones doing this and I am asking myself, why mix the business logic with the data schema?
You can use the repository pattern to extract the data querying from your controller.
class TodoRepository {
public function get_todo($id)
{
return Todo::find($id);
}
public function get_all_todos()
{
return Todo:all();
}
public function create_todo($todo)
{
return Todo::create([
'title' => $todo['title'],
'description' => $todo['description']
]);
}
public function delete_todo($todo)
{
return Todo::find($todo)->delete();
}
}
Then you inject the repository into your controller. That way if you change databases, or ditch eloquent then you just write a new repository with the same interface and you simply change out the injection.
class Todo_Controller extends Base_Controller
{
private $todos;
public function __construct(TodoRepository $todos)
{
$this->todos = $todos;
}
public function action_list() {
return View::make("list")
->with("todos", $this->todos->get_all_todos());
}
public function action_view($id) {
return View::make("view")
->with("todo", $this->todos->get_todo($id));
}
public function action_delete($id) {
$this->todos->delete_todo($id);
return View::make("deleted");
}
public function action_new() {
return View::make("add");
}
public function action_add() {
$todo = $this->todos->create_todo(Input->get('title', 'description');
return View::make("success");
}
}
This was your controller doesn't care how you get_all_todos or delete_todo, it simply asks the repository to get/modify the data then it returns the result.
I have my ApiController and in its constructor
$this->fractal = $fractal;
// Are we going to try and include embedded data?
$this->fractal->setRequestedScopes(explode(',', Input::get('embed')));
Transformer
class PlaceTransformer extends TransformerAbstract
{
protected $availableEmbeds = [
'checkins'
];
public function transform(Place $place)
{
return [
...
];
}
public function embedCheckins(Place $place)
{
$checkins = $place->checkins;
return $this->collection($checkins, new CheckinTransformer);
}
when i try to test
http://myrestapi.dev/places/3?embed=checkins
i get the following error
Call to undefined method League\Fractal\Manager::setRequestedScopes()
Ok i renamed
embed
in
include
and in my ApiController se the following lines
if (isset($_GET['include'])) {
$fractal->parseIncludes($_GET['include']);
}
instead of setRequestedScopes() and all works clear