Laravel phpunit mock model first function - laravel

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();

Related

Write unit test for trait in model laravel

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.

Laravel Notification asserting the data passed down to mail markdown view

I am working on a Laravel project. I am writing integration/ feature tests for my application. I am now writing a test where I need to assert the data passed to the email notification and the data passed to its view. I found this link to do it, https://medium.com/#vivekdhumal/how-to-test-mail-notifications-in-laravel-345528917494.
This is my notification class
class NotifyAdminForHelpCenterCreated extends Notification
{
use Queueable;
private $helpCenter;
public function __construct(HelpCenter $helpCenter)
{
$this->helpCenter = $helpCenter;
}
public function via($notifiable)
{
return ['mail'];
}
public function toMail($notifiable)
{
return (new MailMessage())
->subject("Help Center registration")
->markdown('mail.admin.helpcenter.created-admin', [
'helpCenter' => $this->helpCenter,
'user' => $notifiable
]);
}
}
As you can see in the code, I am passing data to mail.admin.helpcenter.created-admin blade view.
This is my test method.
/** #test */
public function myTest()
{
$body = $this->requestBody();
$this->actingAsSuperAdmin()
->post(route('admin.help-center.store'), $body)
->assertRedirect();
$admin = User::where('email', $body['admin_email'])->first();
$helpCenter = HelpCenter::first();
Notification::assertSentTo(
$admin,
NotifyAdminForHelpCenterCreated::class,
function ($notification, $channels) use ($admin, $helpCenter) {
$mailData = $notification->toMail($admin)->toArray();
//here I can do some assertions with the $mailData
return true;
}
);
}
As you can see my comment in the test, I can do some assertions with the $mailData variable. But that does not include the data passed to the view. How can I assert or get the data or variables passed to the blade view/ template?
As you can see here, there is a viewData property on the MailMessage class which contains all the data passed to the view, no need to turn the notification into an array.
$notification->toMail($admin)->viewData
So it would be something like this in your case:
/** #test */
public function myTest()
{
$body = $this->requestBody();
$this->actingAsSuperAdmin()
->post(route('admin.help-center.store'), $body)
->assertRedirect();
$admin = User::where('email', $body['admin_email'])->first();
$helpCenter = HelpCenter::first();
Notification::assertSentTo(
$admin,
NotifyAdminForHelpCenterCreated::class,
function ($notification, $channels) use ($admin, $helpCenter) {
$viewData = $notification->toMail($admin)->viewData;
return $admin->is($viewData['user']) && $helpCenter->is($viewData['helpCenter']);
}
);
}

Laravel 5.5 - how to mock eloquent database collection

I've decided to make some unit tests for my app and I have a problem.
I have logic part
private $model;
__construct($model)
{
$this->model = $model;
}
public function getData()
{
$data = $this->model->getData();
return $data;
}
model
public function $getData()
{
return self::where('id', '=', 1)->first();
}
and now I would like to make unit test.
I know how to mock right model
$collection = new Collection();
$collection->push(
new Model(
)
);
but have no idea how to put data into it. I've tried put data as attribute but it throws me MassAssigmentException
Would be grateful if someone know and share his knowledge.

Call to undefined method stdClass::delete()

First I make a connection with the CMS trouw API the store method works fine but the update and the delete gives my error undefined method.
If I dd $basket in the delete I get all the data out which means it works fine for the delete method but unfortunately doesn't work, any suggestions.
public function add(Request $request)
{
$productId = $request->get('id');
$amount = $request->get('amount');
$this->basketService->add($productId, $amount);
return redirect()->route('basket.index');
}
public function update(Request $request)
{
foreach ($request->get('amount') as $key => $amount) {
$this->basketService->update($key, $amount);
}
return redirect()->back();
}
public function delete($id)
{
$basket = $this->basketService->getCart();
$basket->delete($id);
return redirect()->route('basket.index');
}
Depends which methods are in basketService Class.
Show source code basketService Class.
(Possible you need call method so: $this->basketService->delete();)

Laravel automatic resolution with parameters

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

Resources