I've written a trait for auto adding some closure model events to model which use this trait.
This is my code:
trait WatchCardListChange
{
public static function booted()
{
static::watchCardListChange();
}
protected static function watchCardListChange()
{
$modelClass = get_called_class();
$classParts = explode('\\', $modelClass);
$className = end($classParts);
$method = 'addEventsTo' . $className . 'Model';
forward_static_call([$modelClass, $method]);
}
private static function addEventsToAcCardModel()
{
static::created(function ($model) {
CardListChanged::dispatch($model);
});
static::updated(function ($model) {
CardListChanged::dispatch($model);
});
static::deleted(function ($model) {
CardListChanged::dispatch($model);
});
}
}
I've searched for writing unit test for this test which use getMockForTrait and this is my code:
public function test_trait()
{
// First approach
$mock = $this->partialMock(AcCard::class, function (MockInterface $mock) {
$mock->shouldAllowMockingProtectedMethods()->shouldReceive('booted')->once();
});
app()->instance(AcCard::class, $mock);
$model = new AcCard();
// Second approach
$trait = $this->getMockForTrait(WatchCardListChange::class, [], '', true, true, true, ['watchCardListChange']);
$trait->expects(self::exactly(1))->method('watchCardListChange');
// Test
$model = AcCard::factory()->create();
}
But both of 2 approaches seem not work.
I wonder what is the best practice to test this trait? Can someone help?
Try using mocking framework Mockery; the technique is overloading a class /methods:
use Mockery as m;
...
m::mock('overload:Full\Namespace\To\WatchCardListChange', function ($mock) {
$mock->shouldReceive('watchCardListChange')
->once()
->andReturn(whatever);
})->shouldIgnoreMissing();
This will go ahead a create a mock in-place; now you can test static call however you want; You can even mock something inside the function itself and when it being called mock object will returned value will be replaced in-place.
Related
I am trying to mock a model where I am doing a query on the db. At first I was mocking the where function on the below but then I realised it's actually first that provides the result, however this still doesn't work. I am aware I could just use the database, but our docker setup is super slow and I can't use SQLite as a previous developer created a migration at some point that removes a foreign key.
Test:
protected function setUp(): void
{
parent::setUp();
$this->calendarEventBookingRepository = app(CalendarEventBookingRepository::class);
$this->calendarEventBooking = Mockery::mock(CalendarEventBooking::class);
}
/** #test */
function bookSingleCustomerReturnsNull()
{
$calendarEvent = factory(CalendarEvent::class)->create();
$calendarEventBooking = factory(CalendarEventBooking::class);
$data = new \stdClass();
$data->customer_id = 1;
$this->calendarEventBooking->shouldReceive('first')->once()->andReturn($calendarEventBooking);
$this->app->instance(CalendarEventBooking::class, $this->calendarEventBooking);
$result = $this->calendarEventBookingRepository->bookSingleCustomer($calendarEvent, $data);
$this->assertEquals(null, $result);
}
Function being tested:
public function bookSingleCustomer(CalendarEvent $event, $data)
{
$this->event = $event;
DB::transaction(function () use ($data) {
$alreadyBooked = $this->modelClassName::where([
['customer_id', $data->customer_id]
])->first();
if ($alreadyBooked) {
return null;
}
return "hello";
});
}
Test Output:
Mockery\Exception\InvalidCountException: Method first(<Any Arguments>) from Mockery_0_Models_CalendarEventBooking should be called
exactly 1 times but called 0 times.
I think you should mock the where method as well.
$this->calendarEventBooking->method('where')->willReturnSelf();
I'm trying to refactor my code to be more reusable.
I created a trait CrudControllerTrait to implement the index,show,store,update,destroy methods.
But I found 2 problems:
BrandController.php
public function store(BrandNewRequest $request)
{
$requestData = $request->validated();
return new BrandResource($this->brands->store($requestData));
}
ProductController.php
public function store(ProductNewRequest $request)
{
$requestData = $request->validated();
return new ProductResource($this->products->store($requestData));
}
The trait method would be:
public function store(xxxxx $request)
{
$requestData = $request->validated();
return new xxxxxResource($this->repository()->store($requestData));
}
Problem1: The hint type. How can I abstract them? If I remove it shows that errror:
"message": "Too few arguments to function App\\Http\\Controllers\\BrandController::store(), 0 passed and exactly 1 expected"
Problem2: Return the resource. How can create the new resource? On the collection I can solve it doing this:
public function index()
{
$models = $this->repository()->index();
return $this->resource()::collection($models);
}
The resource is on the controller who uses the trait:
public function resource()
{
return BrandResource::class;
}
But with single resource didn't know how to do it...
The idea is, that I have so much controllers using the same pattern: BrandController, ProductController, etc. I'd love to reuse these 5 crud methods on the same trait...
The only way I found is creating an abstract method.
trait CrudRepositoryTrait
{
abstract function model();
public function index()
{
return $this->model()::with($this->with())->get();
}
public function find($id)
{
return $this->model()::findOrFail($id);
}
public function store($data)
{
$request = $this->dtoRequest($data);
return $this->model()::create($request);
}
(...)
}
And then, an example how to use this treat:
class ProductRepository implements ProductRepositoryContract
{
use CrudRepositoryTrait;
function model()
{
return Product::class;
}
(...)
}
By this way I could reuse a lot of code.
I am trying to change a value on save
public static function boot()
{
static::saving(function ($formRow) {
$formRow->sales = 1;
});
}
But the weird part is that its not changing the sales to 1, any idea why ?
You're missing the call to the parent's boot method:
public static function boot()
{
// add this
parent::boot();
static::saving(function ($formRow) {
$formRow->sales = 1;
});
}
You can just override the save method on the model itself:
public function save(array $options = [])
Why my static method don´t work with a varible class?
/**
* Events
*/
public static function boot() {
parent::boot();
$class = get_called_class(); // The value os $class is: Product ( string)
// This work
Product::creating(function($model) {
return $model->validate();
});
// Don´t work, the closure never called!
$class::creating(function($model) {
return $model->validate();
});
$class::updating(function($model) {
return $model->validate(true);
});
}
Incredible, this work to:
$class = "Product"; //get_called_class();
Solution ( Not elegant , but ... )
On my Product model i put this, to share class name with Base model.
public static function boot() {
parent::$cls = "Product";
parent::boot();
}
but updating does not work yet!
Actually (since PHP 5.3) it should work to do:
$class = 'Product';
$class::creating(function($model){
return $model->validate();
});
But why not just use static:: to access the current class? This way you don't need get_called_class:
static::creating(function($model){
return $model->validate();
});
There's also call_user_func if everything else fails...
I have a class like this:
class PostValidator
{
public function __construct(Validator $validator, $data)
{
$this->validator = $validator;
$this->data = $data;
}
}
I read Laravel doc about IoC automatic resolution, it gives an example:
class FooBar {
public function __construct(Baz $baz)
{
$this->baz = $baz;
}
}
$fooBar = App::make('FooBar');
Is it possible to use App::make only without App::bind (with closure) to instantiate my class above which also passing parameter $data?
No, you can't do that.
The idea is that you pass only the dependencies to the constructor, and obviously data is not one. Validator works with the data, but does not depend on the data.
Instead use setter for the data.
class PostValidator
{
public function __construct(Validator $validator)
{
$this->validator = $validator;
}
public function setData($data)
{
$this->data = $data;
}
}
and simply call it explicitly:
$validator = App::make('PostValidator');
$validator->setData($data);
// or in the controller, which is what you're doing most likely
public function __construct(PostValidator $validator)
{
$this->validaotr = $validator;
}
public function update($id)
{
$data = Input::only([ input that you need ]);
$this->validator->setData($data);
// run the validation
...
}
edit: as per comment, this is what 2nd argument $parameters does:
// Foo class with Eloquent Models as dependencies
public function __construct(User $user, Category $category, Post $post)
{
$this->user = $user;
$this->category = $category;
$this->post = $post;
}
then IoC container will resolve the dependencies as newly instantiated models:
$foo = App::make('Foo');
$foo->user; // exists = false
$foo->category; // exists = false
$foo->post; // exists = false
but you can do this if you want:
$user = User::first();
$cat = Category::find($someId);
$foo = App::make('Foo', ['category' => $cat, 'user' => $user]);
$foo->user; // exists = true, instance you provided
$foo->category; // exists = true, instance you provided
$foo->post; // exists = false, newly instantiated like before