bwrigley

bwrigley

Member Since 1 Year Ago

Experience Points 5,830
Experience Level 2

4,170 experience to go until the next level!

In case you were wondering, you earn Laracasts experience when you:

  • Complete a lesson — 100pts
  • Create a forum thread — 50pts
  • Reply to a thread — 10pts
  • Leave a reply that is liked — 50pts
  • Receive a "Best Reply" award — 500pts
Lessons Completed 12
Lessons
Completed
Best Reply Awards 0
Best Reply
Awards
  • start-engines Created with Sketch.

    Start Your Engines

    Earned once you have completed your first Laracasts lesson.

  • first-thousand Created with Sketch.

    First Thousand

    Earned once you have earned your first 1000 experience points.

  • 1-year Created with Sketch.

    One Year Member

    Earned when you have been with Laracasts for 1 year.

  • 2-years Created with Sketch.

    Two Year Member

    Earned when you have been with Laracasts for 2 years.

  • 3-years Created with Sketch.

    Three Year Member

    Earned when you have been with Laracasts for 3 years.

  • 4-years Created with Sketch.

    Four Year Member

    Earned when you have been with Laracasts for 4 years.

  • 5-years Created with Sketch.

    Five Year Member

    Earned when you have been with Laracasts for 5 years.

  • school-session Created with Sketch.

    School In Session

    Earned when at least one Laracasts series has been fully completed.

  • welcome-newcomer Created with Sketch.

    Welcome To The Community

    Earned after your first post on the Laracasts forum.

  • full-time-student Created with Sketch.

    Full Time Learner

    Earned once 100 Laracasts lessons have been completed.

  • pay-it-forward Created with Sketch.

    Pay It Forward

    Earned once you receive your first "Best Reply" award on the Laracasts forum.

  • subscriber-token Created with Sketch.

    Subscriber

    Earned if you are a paying Laracasts subscriber.

  • lifer-token Created with Sketch.

    Lifer

    Earned if you have a lifetime subscription to Laracasts.

  • lara-evanghelist Created with Sketch.

    Laracasts Evangelist

    Earned if you share a link to Laracasts on social media. Please email [email protected] with your username and post URL to be awarded this badge.

  • chatty-cathy Created with Sketch.

    Chatty Cathy

    Earned once you have achieved 500 forum replies.

  • lara-veteran Created with Sketch.

    Laracasts Veteran

    Earned once your experience points passes 100,000.

  • 10k-strong Created with Sketch.

    Ten Thousand Strong

    Earned once your experience points hits 10,000.

  • lara-master Created with Sketch.

    Laracasts Master

    Earned once 1000 Laracasts lessons have been completed.

  • laracasts-tutor Created with Sketch.

    Laracasts Tutor

    Earned once your "Best Reply" award count is 100 or more.

  • laracasts-sensei Created with Sketch.

    Laracasts Sensei

    Earned once your experience points passes 1 million.

  • top-50 Created with Sketch.

    Top 50

    Earned once your experience points ranks in the top 50 of all Laracasts users.

10 Jul
5 days ago

bwrigley left a reply on Mockery Executing Original Method

hi @talinon,

Ah, thank you, that makes a lot of sense, and I am definitely guilty of being someone who thought there was some magic going on ?, I assumed that at the point that classes were loaded into memory, the mock would override any methods with the same names.

I will look at the cashier link thank you, that might be really helpful.

Thanks again for your help.

bwrigley left a reply on Mockery Executing Original Method

hi @johnbraun

Thank you for your reply.

Actually yes I'm already doing that. I have a StripeGateway interface which I use in exactly that way and the mocking works fine.

The actual problem hits with the Cashier methods that are added to the User model.

So in my SubscriptionController my store method essentially subscribes the user to a new product and then sets up provisioning of any relevant permissions/dates etc.

For testing purposes the stripe subscription is mocked through the StripeGateway class as you suggest. When the provisioning happens I need to grab the correct start date for this new subscription. This happens on my User model:

   public function setProvisioningDate()
    {
    //

    $startPeriod = $this->subscription('plan')->asStripeSubscription()->current_period_start;
        $nextProvision = Carbon::now()->timestamp($startPeriod)->addMonth();
        $this->next_provisioning = $nextProvision;

    //
    }

the asStripeSubscription() line is a Cashier method which in turn calls the Stripe API.

so what I was actually trying to do was to mock $user->setProvisioningDate() so this line never executes in the test, but the method is being executed anyway.

Apologies, this sounds confusing now, but I hope it makes sense!

bwrigley left a reply on Mockery Executing Original Method

@talinon thank you for replying!

I'm not sure I fully get what you mean here sorry. The line:

$plan = Plan::whereNickname($request->plan)->first();

is just a line in my normal code base in a controller called SubscriptionController where I handle users subscribing to product through stripe. This is not a line in my test class.

My test replicates a user creating a new subscription by posting the correct form to a specific route, which then activates the controller.

In my test, I don't want it to actually hit Stripe's API so I'm mocking out various methods on a couple of controllers to emulate the Stripe responses.

After my controller gets the correct response, it provisions various permissions to the user depending on the package bought.

It's this provisioning that I want to test. However, I am still hitting Stripe each time.

I am just replicating the issue with this testMethod() example. I hope that makes sense?

09 Jul
6 days ago

bwrigley started a new conversation Mockery Executing Original Method

I'm struggling to understand why this isn't working as I'm sure I've had it working before.

In my feature test:

        $product =  factory(Product::class)->create(['name' => 'Test', 'stripe_id' => 'Test']);
        $plan =  factory(Plan::class)->create(['stripe_id' => 'Test', 'product_id' => $product->id]);

        $mockPlan = Mockery::mock(Plan::class);
        $this->app->instance(Plan::class, $mockPlan);
        $mockPlan->shouldReceive('testMethod')
            ->andReturn('mock executed');

        $subscriptionDetails = [
            'plan' => $plan->nickname,
            'stripeToken' => 'randomTokenString',
        ];

        $this->fromUrl('/dashboard')
            ->post('/subscription/create', $subscriptionDetails)
            ->assertRedirect('/dashboard');

in my Plan class:

    public function testMethod()
    {
        return 'original method executed';
    }

in a class that is executed during the test:

            $plan = Plan::whereNickname($request->plan)->first();

            dump($plan->testMethod());

the output is always: "original method executed"

I'm sure I'm missing something obvious, but can't see anything in the Laravel or Mockery docs that show to do anything different.

Thanks for your help in advance!

19 Jun
3 weeks ago

bwrigley started a new conversation Counting Grandchildren

I'm looking through the eloquent documentation and I'm not quite finding what I woud like. hasManyThrough seems to be the closest but doesn't quite cut it.

So in my app a User owns many ForumThreads

class User extends Model
{
    public function forumThreads(): HasMany
    {
        return $this->hasMany('App\Forum\ForumThread');
    }
}

and a ForumThread can have many ForumLikes

class ForumThread extends Model
{
    public function forumLikes(): MorphMany
    {
        return $this->morphMany('App\Forum\ForumLike', 'likeable');
    }
}

What I had naively hoped was that I could say

$userLikes = $user->forumThreads->forumLikes->count();

is there an eloquent way to do this or do I need to simply loop through?

Thanks!

18 Jun
3 weeks ago

bwrigley started a new conversation Scout ShouldBeSearchable() Called Before Saved?

I have a ForumThread model and a FormCategory model.

Relationship is defined on ForumThread model as:

    public function forumCategory(): BelongsTo
    {
        return $this->belongsTo('App\Forum\ForumCategory');
    }

I am now wanting to add these ForumThreads to a new Algolia index but only if the 'ForumCategoryis not defined as admin only. So I am usingshouldBeSearchable()' :

    public function shouldBeSearchable(): bool
    {
        return $this->forumCategory->is_admin_only === false;
    }

When I now save any thread (ie with any category) I get the error Trying to get property 'is_admin_only' of non-object

I have the feeling that this is run before the thread is actually saved and so does not have this relationship yet?

Any thoughts would be gratefully received. Thanks

14 Jun
1 month ago

bwrigley left a reply on Query Relationship Model

ah! contains is the magic word. Thank you so much!

bwrigley started a new conversation Query Relationship Model

I'm sure this is much simpler than I am making it but I don't seem to be able to get it to work.

I have a forum which has ForumThreads ForumSubscribers.

As this is a many-to-many relationship I have set it up like this:

        Schema::create('forum_subscriber_forum_thread', function (Blueprint $table) {
            $table->integer('forum_subscriber_id')->unsigned()->nullable();
            $table->foreign('forum_subscriber_id')->references('id')
                                ->on('users')->onDelete('cascade');

            $table->integer('forum_thread_id')->unsigned()->nullable();
            $table->foreign('forum_thread_id')->references('id')
                                ->on('forum_threads')->onDelete('cascade');

            $table->timestamps();
            $table->unique(['forum_subscriber_id', 'forum_thread_id'], 'subscriber_thread_unique');
        });

and in my ForumThread model I have the relationship defined like this:

    public function forumSubscribers(): BelongsToMany
    {
        return $this->belongsToMany('App\User', 'forum_subscriber_forum_thread', 'forum_thread_id', 'forum_subscriber_id');
    }

All working fine, great!

Now I want to check if a User is subscribed to a ForumThread

I thought this would be:

 public function isUserSubscribed(User $user): bool
 {
    return $this->forumSubscribers->where(['forum_subscriber_id',$user->id])->count() > 0;
 }

and then I tried

    public function isUserSubscribed(User $user): bool
    {
        return $this->forumSubscribers()->with(['Users' => function ($q) use ($user): void {
            $q->where('id', $user->id);
        }])->count() > 0;
    }

Neither of these work, I'm sure this must be simpler than I am making it?

Thanks!

10 Jun
1 month ago

bwrigley left a reply on Picking Up Implicit Model In Middleware

Thank you both!

I swear I had tried both of these before I posted, but they both seem to be working now. Huge apologies and thank you.

bwrigley started a new conversation Picking Up Implicit Model In Middleware

Hello,

I'm building a simple forum and I want to write some middleware that inspects a ForumThread to make sure it's not locked/archived/etc. before accepting route requests for ForumReply.

But I'm not sure how to pick up the implicitly bound thread that i'm passing in.

So my routes look like this:

             Route::middleware(['forum.thread.unlocked'])->group(function (): void {
                Route::group(['prefix' => 'forum_reply', 'as' => 'forum_reply.'], function (): void {
                    Route::get('create/{forum_thread}', 'Forum\[email protected]')->name('create');
                    Route::post('store/{forum_thread}', 'Forum\[email protected]')->name('store');
                    Route::post('resolve/{forum_thread}', 'Forum\[email protected]')->name('resolve');
                });
            });

In my controller I pick up the ForumThread from the Request in the usual way:

    public function create(ForumThread $thread) : View
    {
    //
    }

How can I also do the same in my middleware as the Request attributes appear to be empty:

https://domain.test/forum/forum_reply/create/215

    public function handle(Request $request, Closure $next)
    {
        dump($request->attributes);
    }

ParameterBag {#44 ▼ #parameters: []

09 May
2 months ago

bwrigley left a reply on Many To Many Relationship Creation

@CBACONNIER - ah yes! Thank you, I had completely forgotten about the naming convention. And thank you for the reminder about name of the ID field. Actually it turns out it needs both of them in this instance.

    public function forumSubscribers(): BelongsToMany
    {
        return $this->belongsToMany('App\User', 'forum_subscriber_forum_thread', 'forum_thread_id', 'forum_subscriber_id');
    }

Thank you again!

07 May
2 months ago

bwrigley started a new conversation Many To Many Relationship Creation

I have a model called ForumThread which has one-to-one relationship to the User model as the person that owns it. However, it also needs a many-to-many relationship to the User model as the people that subscribe to it.

I've defined these relationships in ForumThread:

    public function user(): BelongsTo
    {
        return $this->belongsTo('App\User');
    }

    public function forumSubscribers(): BelongsToMany
    {
        return $this->belongsToMany('App\User');
    }

and I've created the pivot table

        Schema::create('forum_subscriber_forum_thread', function (Blueprint $table) {
            $table->integer('forum_subscriber_id')->unsigned()->nullable();
            $table->foreign('forum_subscriber_id')->references('id')
                                ->on('users')->onDelete('cascade');

            $table->integer('forum_thread_id')->unsigned()->nullable();
            $table->foreign('forum_thread_id')->references('id')
                                ->on('forum_threads')->onDelete('cascade');

            $table->timestamps();
        });

I think I have done that correctly. However there is never a time I want to create ForumThread 'through' a Subscriber, nor do I want to create User 'through' the ForumThread. I think I should create the User then the ForumThread and then link them through the Subscriber relationship.

    public function toggleSubscription(string $userID): void
    {
       $this->forumSubscribers()->toggle([$userID]);
    }

however this gives me a SQL error:

SQLSTATE[42S02]: Base table or view not found: 1146 Table 'tcl.forum_thread_user' doesn't exist (SQL: select `user_id` from `forum_thread_user` where `forum_thread_id` = 5)

I'm not clear why it's looking for a table called forum_thread_user. I'm sure this will be a typo on part, but can anyone shed some light?

Thank you for reading.

30 Apr
2 months ago

bwrigley left a reply on Deleting Parent Model Without Cascade

Brilliant! Thank you for such a quick response.

p.s. where did you find it? I thought I had searched everywhere!

bwrigley started a new conversation Deleting Parent Model Without Cascade

This might be more of a SQL question but I'm curious how to handle this correctly in Laravel.

I have a model called ForumThread that I have linked to my User model

Schema::create('forum_threads', function (Blueprint $table) {
    //
    $table->integer('user_id')->nullable()->unsigned();
    
    $table->foreign('user_id')
             ->references('id')
            ->on('users');
}

I didn't set a cascading delete as I want to be able to delete a User without deleting the ForumThread but leaving it orphaned.

However, I get a constraint violation when I try to delete the User:

Integrity constraint violation: 1451 Cannot delete or update a parent row: a foreign key constraint fails (`tcl`.`forum_threads`, CONSTRAINT `forum_threads_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`))

Please can someone tell me the correct way to achieve what I'm trying to acheive. Thank you for your help.

26 Apr
2 months ago

bwrigley left a reply on Problem Picking Up Model In Route Model Binding

And this is exactly what I had forgotten I had done with all the others. Thank you!

Still unclear how the implicit binding should actually work, but will come back to that at another time...

Thanks!

bwrigley left a reply on Problem Picking Up Model In Route Model Binding

Yeah I will, I have loads of these to do though so will try and get to the bottom of the naming.

Thanks!

bwrigley left a reply on Problem Picking Up Model In Route Model Binding

I wondered if it was a naming issue too so I've tried forumCategory, forum_category and just category but all have the same effect.

However, what you have suggested above does work, so perhaps I should just stick with that.

bwrigley left a reply on Problem Picking Up Model In Route Model Binding

@snapey

As always you are correct, and I get the logic, but I'm confused why this works on other models. Here is my admin model:

Routes:

    Route::group(['middleware' => 'admin', 'prefix' => 'admin', 'as' => 'admin.'], function (): void {
        Route::view('/', 'admin.dashboard')->name('dashboard');
        Route::resource('administrator', 'AdminController');
        Route::group(['prefix' => 'forum', 'as' => 'forum.'], function (): void {
            Route::resource('category', 'Forum\ForumCategoryController');
        });
|        | POST      | admin/administrator                      | admin.administrator.store    | App\Http\Controllers\[email protected]                                  | web,authenticated,admin
                         |
|        | GET|HEAD  | admin/administrator                      | admin.administrator.index    | App\Http\Controllers\[email protected]                                  | web,authenticated,admin
                         |
|        | GET|HEAD  | admin/administrator/create               | admin.administrator.create   | App\Http\Controllers\[email protected]                                 | web,authenticated,admin
                         |
|        | DELETE    | admin/administrator/{administrator}      | admin.administrator.destroy  | App\Http\Controllers\[email protected]                                | web,authenticated,admin
                         |
|        | PUT|PATCH | admin/administrator/{administrator}      | admin.administrator.update   | App\Http\Controllers\[email protected]                                 | web,authenticated,admin
                         |
|        | GET|HEAD  | admin/administrator/{administrator}      | admin.administrator.show     | App\Http\Controllers\[email protected]                                   | web,authenticated,admin
                         |
|        | GET|HEAD  | admin/administrator/{administrator}/edit | admin.administrator.edit     | App\Http\Controllers\[email protected]                                   | web,authenticated,admin

AdminController:

    public function update(AdminFormRequest $request): RedirectResponse
    {
        return $this->upsert($request);
    }

    public function upsert(AdminFormRequest $request): RedirectResponse
    {
        $admin = $request->route('administrator');
    dd($admin);
        //
    }

This does dump the model and not the ID.

25 Apr
2 months ago

bwrigley left a reply on Problem Picking Up Model In Route Model Binding

Yup that all works fine and the edit form is populated with all the relevant information I'd expect to see.

The use of id is pretty standard with route model binding, and it's the primary key they demonstrate with in the docs:

https://laravel.com/docs/5.8/routing#route-model-binding

bwrigley left a reply on Problem Picking Up Model In Route Model Binding

it's also implicit in the route model binding so my edit() method is actually:

    public function edit(ForumCategory $category): View
    {
        return view('forum.category.edit', compact('category'));
    }

which in turn is called from another index view with:

    {{ Form::model($category, array('route' => array('admin.forum.category.edit', $category->id)))}}
            @method('GET') 
            @csrf 
            <button id="submit" type="submit">View/Edit</button> 
    {{ Form::close() }}

bwrigley started a new conversation Problem Picking Up Model In Route Model Binding

I know I'm doing something stupid here as I have this working for other models, but can't figure out why it's not working for my ForumCategory model.

The problem is that in my edit() method in my controller I expect to implicitly collect the model from the Request, but I am just getting the id.

The route nesting is slightly complex for this model which may be part of the issue:

Routes:

    Route::group(['middleware' => 'admin', 'prefix' => 'admin', 'as' => 'admin.'], function (): void {
        Route::view('/', 'admin.dashboard')->name('dashboard');
        Route::resource('administrator', 'AdminController');
        Route::group(['prefix' => 'forum', 'as' => 'forum.'], function (): void {
            Route::resource('category', 'Forum\ForumCategoryController');
        });
        Route::post('impersonate', 'Admin\[email protected]')->name('impersonate.store');
    });

$php artisan route:list :

|        | POST      | admin/forum/category                     | admin.forum.category.store   | App\Http\Controllers\Forum\[email protected]                    | web,authenticated,admin
                         |
|        | GET|HEAD  | admin/forum/category                     | admin.forum.category.index   | App\Http\Controllers\Forum\[email protected]                    | web,authenticated,admin
                         |
|        | GET|HEAD  | admin/forum/category/create              | admin.forum.category.create  | App\Http\Controllers\Forum\[email protected]                   | web,authenticated,admin
                         |
|        | PUT|PATCH | admin/forum/category/{category}          | admin.forum.category.update  | App\Http\Controllers\Forum\[email protected]                   | web,authenticated,admin
                         |
|        | DELETE    | admin/forum/category/{category}          | admin.forum.category.destroy | App\Http\Controllers\Forum\[email protected]                  | web,authenticated,admin
                         |
|        | GET|HEAD  | admin/forum/category/{category}          | admin.forum.category.show    | App\Http\Controllers\Forum\[email protected]                     | web,authenticated,admin
                         |
|        | GET|HEAD  | admin/forum/category/{category}/edit     | admin.forum.category.edit    | App\Http\Controllers\Forum\[email protected]                     | web,authenticated,admin

Edit view:

{{ Form::model($category, array('route' => array('admin.forum.category.update', $category->id))) }}

    @method('PUT')

    @include ('forum.category.partials.categoryForm')

    <button id="submit" type="submit">Update</button>

{{ Form::close() }}

ForumCategoryController:

    public function update(ForumCategoryFormRequest $request): RedirectResponse
    {
        return $this->upsert($request);
    }

    public function upsert(ForumCategoryFormRequest $request): RedirectResponse
    {

    dd($request->route('category'));

//

this dumps the id, not the model as I was expecting.

Any thoughts appreciated.

12 Apr
3 months ago

bwrigley left a reply on Using Laravel Cashier How Can I Access User's Subscribed Plan?

@BRAUNSON - @braunson

Thanks for your reply. Yes I'm familiar with how to get the user's subscription but it's the plan I'm interested in and then the product associated to plan.

In my set up, different products grant the user different permissions, so when I set a user's permissions I need to access the product they have purchased which would ideally be:

$user->subscription->plan->product;

I can get to the product but through a more convoluted route and was wondering if there was a better way.

Thanks!

06 Apr
3 months ago

bwrigley started a new conversation Laravel Cashier Access Current Plan Model

Is there a way to access the plan a User is subscribed to directly from the description?

$user->subscription('default')->stripe_plan;

gives me the id and I can look up the plan from that, but I'm curious why there isn't

$plan = $user->subscription('default')->plan;

perhaps there is something similar that I haven't spotted?

04 Apr
3 months ago

bwrigley left a reply on Notifications Using UUID

Just stumbled across this whilst looking for something else.

UUID is far more scalable for larger applications potentially over distributed databases

25 Mar
3 months ago

bwrigley left a reply on Using Mockery, But Original Method Still Being Called

@talinon - thank you again. I was just getting the elements of code together to show you and I'm embarrassed to admit that I found out that createUser() is actually getting called by a job which is basically running in realtime so I didn't spot it!

So sorry to waste your time! Now I just need to find out how I can fake this job in every test but not other jobs. But that's a different problem!

Thank you again. So grateful.

bwrigley left a reply on Using Mockery, But Original Method Still Being Called

@talinon so sorry to come back to this, but now I have written the full test, I seem to be having the same issue again.

Firstly, I have updated Mockery as you suggested and that is now all working fine as we had it above.

Now the real test looks like this:

        $mock = Mockery::mock(IntercomGateway::class)->makePartial();
        $mock->shouldReceive('createUser')->andReturn('mocked');
        $this->app->instance(IntercomGateway::class, $mock);

        $user = $this->createTestUser();

        $loginCredentials = [
            'email' => $user->email,
            'password' => 'wrong_password',
        ];

        $this->fromUrl(route('login'))
            ->post('/login', $loginCredentials)
            ->assertEIMErrorExists('login_fail');

the createTestUser() method sets up a user just for the purposes of the test, but also creates a new Intercom user which is the bit I want to mock as it's not relevant to the test.

However, running the above produces Original method executed as before.

So sorry to ask you again, but would appreciate your thoughts on what I might be doing wrong here.

Thank you!

22 Mar
3 months ago

bwrigley left a reply on Using Mockery, But Original Method Still Being Called

Thank you so much, so grateful for your time. No way I would have figured that one out!

bwrigley left a reply on Using Mockery, But Original Method Still Being Called

@talinon you genius, it works! Thank you.

However I do not understand that at all!

bwrigley left a reply on Using Mockery, But Original Method Still Being Called

my method looks like this at the moment:

    public function createUser(User $user): void
    {

        dump('Original method executed');
        try{
            Intercom::users()->create([
                'user_id' => $user->id,
                'name' => $user->fullName,
                'email' => $user->email,
                'phone' => optional($user->profile)->getPrimaryTelephone(),
                'signed_up_at' => $user->created_at,
            ]);
        } catch (\GuzzleHttp\Exception\ConnectException $e) {
            Log::Alert('Cannot connect to Intercom ' . $e->getCode() . ' ' . $e->getMessage());
        }
    }

bwrigley left a reply on Using Mockery, But Original Method Still Being Called

@TaI've tried yours and I get mocked too. So it's something to do with my class?

bwrigley left a reply on Using Mockery, But Original Method Still Being Called

@talinon thanks for such a fast response!

I have tried that and now I get null but I think I should get mocked no?

bwrigley started a new conversation Using Mockery, But Original Method Still Being Called

I'm sure I'm missing something really obvious here as I have mocks with Mockery working elsewhere in my code.

This is what I have in a test currently:

        $mock = Mockery::mock(IntercomGateway::class)->makePartial();
        $mock->shouldReceive('createUser')->andReturn('nothing');
        $this->app->instance(IntercomGateway::class, $mock);

        $gw = new IntercomGateway;

        dump($gw->createUser(new \App\User));

I have also added this line to the original createUser()

dump('Original method executed');

When I run the test I see Original method executed

Any thoughts on what I might have missed? Thank you

21 Mar
3 months ago

bwrigley started a new conversation 'Risky' Test

I have a feature test which is giving me this error:

Test code or tested code did not (only) close its own output buffers

which I understand can be triggered by the buffer being larger/smaller than expected. But I have no idea who to begin tracing that.

I have isolated the issue to this call in my test calls followRedirects()

        $loginCredentials = [
            'email' => $this->email,
            'password' => $this->password,
        ];

        $this->fromUrl(route('dashboard'))
            ->post('/login', $loginCredentials)
            ->followRedirects()
            ->assertViewIs('subscription.editCard');

which is a macro that I use across ~150 tests with no problem

        TestResponse::macro('followRedirects', function ($testCase = null) use ($test) {
            $response = $this;
            $testCase = $testCase ?: $test;

            while ($response->isRedirect()) {
                $response = $testCase->get($response->headers->get('Location'));
            }

            return $response;
        });

if I dump the contents of the HTML produced by my post I get a pretty standard redirect:

<!DOCTYPE html>\n
<html>\n
    <head>\n
        <meta charset="UTF-8" />\n
        <meta http-equiv="refresh" content="0;url=http://domain.test/dashboard" />\n
\n
        <title>Redirecting to http://domain.test/dashboard</title>\n
    </head>\n
    <body>\n
        Redirecting to <a href="http://domain.test/dashboard">http://domain.test/dashboard</a>.\n
    </body>\n
</html>

if I dump after the followRedirects() I see the HTML output of the view I'm expecting.

Does anyone know how I can trace the issue here?

13 Mar
4 months ago

bwrigley left a reply on How To Test Mail Sent From Notification

@nash that's the one brilliant thank you! Yes I wasn't wanting to test the actual mail was sent but couldn't figure out how to do what you have just suggested.

Thanks again!

12 Mar
4 months ago

bwrigley left a reply on How To Test Mail Sent From Notification

@nash

Thanks for your reply, and that's great for testing Notifications but Notification::assertSentTo() is channel agnostic.

So if the user's preferred channel is nexmo then Notification::assertSentTo() will be true or if the channel is 'database' then 'Notification::assertSentTo()' is also true.

I specifically want to make sure that I am routing instant notifications via the mail channel if that's the channel the user has accepted, so I need to test if an email has been sent.

I hope that makes sense.

bwrigley left a reply on Testing Notifications

In the end I have opted to create a TestNotification Notification, which feels ugly, but it does the trick.

bwrigley started a new conversation How To Test Mail Sent From Notification

My users can specify if they have Notifications sent to them instantly or to be notified daily of any pending Notifications.

This all works fine with manual testing, so now I'd like to write a feature test around this. This is what I have so far:

    public function test_instant_notification_is_sent(): void
    {
        Mail::fake();

        $this->user = $user = factory(User::class)->create();

        $this->user->update(
            [
                'notification_frequency' => 'instant',
                'notification_method' => 'email',
                'notification_email' => $this->email,
            ]
        );

        $this->user->save();

        $email = ['subject' => 'subject', 'data' => 'data'];
        $sms = 'Test Notification';
        $database = ['some' => 'data'];

        $this->user->notify(new TestNotification($email, $sms, $database));

        Mail::assertSent(MailMessage::class);

and I've written the TestNotification to go with it:

class TestNotification extends Notification
{
    use Queueable;

    /**
     * Create a new notification instance.
     *
     * @param string[] $email
     * @param string $sms
     * @param string[] $database
     * @return void
     */
    public function __construct(array $email, string $sms, array $database)
    {
        $this->email = $email;
        $this->sms = $sms;
        $this->database = $database;
    }

    /**
     * Get the notification's delivery channels.
     *
     * @param  mixed  $notifiable
     * @return string[]
     */
    public function via($notifiable): array
    {
        return [$notifiable->preferredNotificationMethod()];
    }

    /**
     * @param  mixed  $notifiable
     * @return string[]
     */
    public function toDatabase($notifiable): array
    {
        return $this->database;
    }

    /**
     * @param  mixed  $notifiable
     * @return string[]
     */
    public function toNexmo($notifiable): array
    {
        return (new NexmoMessage)
            ->content($this->sms);
    }

    /**
     * @param  mixed  $notifiable
     * @return string[]
     */
    public function toMail($notifiable): MailMessage
    {
        return (new MailMessage)
            ->greeting($this->email['subject'])
            ->line('Testing Notifications!')
            ->line('Thank you for using our application!');
    }
}

So my test fails because, as I now see, MailMessage is not a mailable and I think Mail::fake() and the mail assertions only work with mailables.

Incidentally, if I remove Mail::fake() the email is sent fine.

How have others managed to test this please without actually sending the email.

bwrigley left a reply on Scheduler Caching Job?

Solved! Embarrassingly I forgot:

protected $frequency;

11 Mar
4 months ago

bwrigley left a reply on Testing Notifications

@PAULCLARKE - No problem. Thanks again!

bwrigley left a reply on Scheduler Caching Job?

So I was doing this:

$schedule->job(new NotificationsPending('morning'))->dailyAt('8:00');

which is what the docs say, and causes the error above.

however, this works instead:

$schedule->call(function (): void {
       $job = new NotificationsPending('evening');
       $job->handle();
 })->dailyAt('8:00');

bwrigley left a reply on Testing Notifications

@paulclarke

Out of interest, how do you test instant notifications? As these don't touch the database DatabaseNotification can't be used.

Thanks!

bwrigley left a reply on Testing Notifications

@borisu

Thank you, but for the functionality to test, I want to make sure that it works with or without notifications and with different user settings.

Thanks though!

bwrigley left a reply on Testing Notifications

@PAULCLARKE - Brilliant! Thank you. It was the DatabaseNotification class I couldn't find I was trying 'Notification'.

Slightly cheeky to point out that this also needs:

'notifiable_type'  => 'App\User'

in the factory or passed in on creation.

Thank you again!

bwrigley started a new conversation Testing Notifications

I have set up my project so that a user can specify if they would like their notifications instantly, daily, weekly and via sms, email, or just in their dashboard.

So if a user has said 'instantly' then I fire off an sms/email right away.

If they have said daily/weekly, then I run a job with the Scheduler which checks (at the right time) if a user has any unread notifications and send them an email/sms alerting them to how many unread notifications they have.

All good. Now I want to write a test around this.

This means I need to seed the database with some notifications against a user's account, but I can't figure out a good, clean, reproducible way to do this. I'm sure I'm just being dumb. The options I've thought of are:

  1. In my test, create a number of instances of an existing Notification class. This feels wrong as my test shouldn't expect the existence of a certain Notification class to run.
  2. Create a TestNotification class. This is doable, but seems messy and under which namespace would it be stored? Along with the other Notification classes?
  3. Seed the database directly with raw queries. Again feels messy not to use the mechanisms that are built-in for notifications, and what would I store in the type field?
  4. Write a factory around the Notification model. I tried this but couldn't get it to work, but maybe this is the correct way?

I would really appreciate your thoughts.

Thanks

Ben

08 Mar
4 months ago

bwrigley left a reply on Scheduler Caching Job?

not sure if this helps but I've noticed something else.

here are my constructor and my handle:

    public function __construct(string $frequency)
    {
        $this->frequency = $frequency;
        Log::debug('here ' . $this->frequency);
    }

    public function handle(): void
    {
        $dbUsers = DB::table('users')
            ->leftJoin('notifications', 'users.id', '=', 'notifications.notifiable_id')
            ->selectRaw('users.id as id , count(*) as pending')
            ->where('notification_frequency', $this->frequency)
            ->where('read_at', null)
            ->groupby('users.id')
            ->get();

        Log::Info('SCHEDULE JOB: NotificationsPending:' . $this->frequency . ' sending to ' . count($dbUsers) . ' users');

        foreach ($dbUsers as $dbUser) {
            $user = User::find($dbUser->id);
            $user->notify(new PendingNotifications($dbUser->pending, $this->frequency));

        }
    }

I've cleared the cache by restarting homestead to I know this is the code that's running.

The Log in the constructor works fine, but the handle fails

Undefined property: App\Jobs\NotificationsPending::$frequency which refers to the line in the query.

bwrigley left a reply on Scheduler Caching Job?

hi @snapey,

Yes it terminates when I run in tinker.

07 Mar
4 months ago

bwrigley started a new conversation Scheduler Caching Job?

Please forgive me if this is an obvious question but does the scheduler cache classes? Or perhaps unique to using homestead?

The reason I ask is that my scheduler runs a job which has standard __construct() and handle() methods.

If I change something in the __construct() method like add a logging command, it logs fine next time the scheduler runs. However, if I change code in the handle() method nothing changes until I reboot homestead.

I can't find anyone else with this issue. Am I missing something obvious?

Thanks!

06 Mar
4 months ago

bwrigley left a reply on Selecting Model On Join With Count

@JLRDW - ah! I wasn't grouping! Thanks (which means I don't need to query pending)

bwrigley left a reply on Selecting Model On Join With Count

@ANONYMOUSE703 - no it's not but it sql you can usually query against an aliased field I thought

bwrigley left a reply on Selecting Model On Join With Count

@RDELORIER - no pending column, only the alias created in the statement