I'm trying to write a Laravel PHPUnit test that checks if a mail has been queued after a user was created.
<?php
namespace Tests\Unit\User;
use App\User;
use Tests\TestCase;
use App\Notifications\UserCreated;
use Illuminate\Support\Facades\Mail;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Support\Facades\Notification;
use Illuminate\Foundation\Testing\RefreshDatabase;
class UserUnitTest extends TestCase
{
use RefreshDatabase;
/**
* check if a user was created in database
*
* #return void
*/
public function testUserCreate()
{
$user = factory(User::class)->create();
$this->assertDatabaseHas('users', [
'email' => $user->email,
'active' => 0,
'activation_token' => $user->activation_token,
'deleted_at' => NULL
]);
}
/**
* check if email was sent after user was created in database
*
* #return void
*/
public function testEmailSentAfterUserCreated()
{
Notification::fake();
// Assert that no notifications were sent...
Notification::assertNothingSent();
$user = factory(User::class)->create();
// Assert a notification was sent to the given users...
Mail::assertQueued(UserCreated::class, 1);
}
}
When I run this test testEmailSentAfterUserCreated it throws the following exception.
There was 1 error:
1) Tests\Unit\User\UserUnitTest::testEmailSentAfterUserCreated
BadMethodCallException: Method Illuminate\Mail\Mailer::assertQueued
does not exist.
/home/vagrant/Projects/endiro/vendor/laravel/framework/src/Illuminate/Support/Traits/Macroable.php:103
/home/vagrant/Projects/endiro/vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php:245
/home/vagrant/Projects/endiro/tests/Unit/User/UserUnitTest.php:49
The Mail class has been included and I'm sure the arguments are correct but I'm not sure why I'm getting this error.
Use Mail::fake() if you want to assert on Mail::assertQueued(). I was facing the same issue. I forgot to add Mail::fake() in that particular test case.
Notifications does not have an assert queued, it has an assertSentTo().So an example of how it should look. If the notification can be queued, i would think you could use the Queue::fake() to achieve this.
Notification::assertSentTo(
[$user], UserCreated::class
);
Related
I am quite new to unit testing and could do with a bit of guidance. I am trying to write a simple unit test using the Factory pattern in Laravel and phpunit for an application that allows you to add Companies to a database.
I have a Company Model Company.php, a Factory class which uses it CompanyFactory.php, and finally the unit test itself CompaniesTest.php.
Models/Company.php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Company extends Model
{
use HasFactory;
protected $table = 'companies';
protected $fillable = [
'name',
'email'
];
}
Database/Factories/CompanyFactory.php
namespace Database\Factories;
use App\Models\Company;
use Illuminate\Database\Eloquent\Factories\Factory;
class CompanyFactory extends Factory
{
/**
* The name of the factory's corresponding model.
*
* #var string
*/
protected $model = Company::class;
/**
* Define the model's default state.
*
* #return array
*/
public function definition()
{
return [
'name' => $this->faker->name,
'email' => $this->faker->email,
'created_at' => now(),
'updated_at' => now(),
];
}
}
Tests/Feature/CompaniesTest.php
namespace Tests\Unit;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use App\Models\Company;
use Tests\TestCase;
use Illuminate\Support\Str;
use Illuminate\Foundation\Testing\WithFaker;
class CompanyTest extends TestCase
{
use WithFaker, DatabaseTransactions;
public function createCompany($name = NULL)
{
if ($name == NULL) $name = Str::random(6);
$company = Company::factory()->create([
'name' => 'TestName_'.$name
]);
return $company->id;
}
/** #test */
public function company_can_be_created()
{
$name = Str::random(6);
//Create a company
$company_id = $this->createCompany($name);
//Check whether the company name exists in the database
$this->assertDatabaseHas('companies', [
'name' => 'TestName_'.$name
]);
}
}
The test seems to work, but it feels like I might have over-complicated it and probably not followed the correct conventions.
What would be a better way to structure it?
The test looks ok, but what are you actually testing? It seems to me that this test is testing the framework's code, which is actually not what you should do.
Don't test a factory, use it to prepare the data needed before each test. And then run your actual code which you want to test, and assert results.
Update: Continue with CompanyVerifier (see comments). Suppose company can be valid and non-valid. Valid companies can be verified. Then a test may look like:
/** #test */
public function test_valid_company_can_be_verified()
{
// here use a CompanyFactory with some pre-defined data to create "valid" company
$validCompany = $this->createValidCompany();
// here goes the actual code of SUT (system under test)
$verifier = new CompanyVerifier();
$result = $verifier->verify($validCompany);
// here check results
$this->assertTrue($result);
}
The good practice for testing is named AAA (arrange-act-assert). Here the creation of a company with some state is an "arrange" stage. Running tested code is "act". And assert is "assert".
Factories are just a helper for "arrange" stage.
I'm trying to send an email using laravel however I keep getting the cannot access empty property error whenever I run my code.
I've done my research on the error and it seems to be usually caused by using a $ before the property name, example $this->$username instead of $this->username. However, that isn't the case in my code.
I can't really tell what's causing it nor do I have great experience in Laravel
Here's my mailable class:
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Queue\ShouldQueue;
class VerificationMail extends Mailable
{
use Queueable, SerializesModels;
public $data;
/**
* Create a new message instance.
*
* #return void
*/
public function __construct($data)
{
$this->data = $data;
}
/**
* Build the message.
*
* #return $this
*/
public function build()
{
$data2 = ['companyName' => $this->data['name'], 'verificationCode' => $this->data['verificationCode']];
return $this->from('noreply#REMOVED.com')
->$this->view('emails.verification', $data2);
}
}
My view is saved in resources/views/emails/verification.blade.php
I saw also that this error can sometimes be caused by using $message as variable name inside the views, however that isn't the case with me. I tried loading the view with a normal route without any mail sending involved and it loaded normally.
Can anyone spot it? Thanks.
You have error here:
return $this->from('noreply#REMOVED.com')
->$this->view('emails.verification', $data2);
Use following instead: (remove second $this->)
return $this->from('noreply#REMOVED.com')
->view('emails.verification', $data2);
This is my first time using events in Laravel/Lumen.
I am actually using Lumen and I am trying to dispatch an instance of Mailable when a new user signs up in order to send an email in the background.
I believe I have set it up right, but I keep getting this error...
Type error: Argument 1 passed to Illuminate\Mail\Mailable::queue() must implement interface Illuminate\Contracts\Queue\Factory, instance of Illuminate\Queue\DatabaseQueue given
I can't actually see within the error message itself where the issue is coming from e.g. there is no line numbers.
However, this is my code...
AuthenticationContoller.php
$this->dispatch(new NewUser($user));
NewUser.php
<?php
namespace App\Mail;
use App\Models\User;
use Illuminate\Mail\Mailable;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Queue\ShouldQueue;
class NewUser extends Mailable implements ShouldQueue
{
use Queueable, SerializesModels;
protected $user;
public function __construct(User $user)
{
$this->user = $user;
}
/**
* Build the message.
*
* #return $this
*/
public function build()
{
return $this->view('test')->to('test#test.com', 'Test')
->from('test#test.com', 'test')->replyTo('test#test.com', 'test')
->subject('Welcome to the blog!');
}
}
I ran into the same issue. It seems like Lumen and Illuminate/Mailer don't work together all that well.
However I found quite an easy fix in a Github thread.
Basically you just have to create a new service provider in your app/Providers directory.
MailServiceprovider.php
<?php
namespace App\Providers;
use Illuminate\Mail\Mailer;
use Illuminate\Mail\MailServiceProvider as BaseProvider;
class MailServiceProvider extends BaseProvider
{
/**
* Register the Illuminate mailer instance.
*
* #return void
*/
protected function registerIlluminateMailer()
{
$this->app->singleton('mailer', function ($app) {
$config = $app->make('config')->get('mail');
// Once we have create the mailer instance, we will set a container instance
// on the mailer. This allows us to resolve mailer classes via containers
// for maximum testability on said classes instead of passing Closures.
$mailer = new Mailer(
$app['view'], $app['swift.mailer'], $app['events']
);
// The trick
$mailer->setQueue($app['queue']);
// Next we will set all of the global addresses on this mailer, which allows
// for easy unification of all "from" addresses as well as easy debugging
// of sent messages since they get be sent into a single email address.
foreach (['from', 'reply_to', 'to'] as $type) {
$this->setGlobalAddress($mailer, $config, $type);
}
return $mailer;
});
$this->app->configure('mail');
$this->app->alias('mailer', \Illuminate\Contracts\Mail\Mailer::class);
}
}
And then you just have to register this service provider in your bootstrap/app.php instead of the default one by adding the following line:
$app->register(\App\Providers\MailServiceProvider::class);
I have a controller which mainly has two functions. First function adds the user to the database and sends an email (receipt.blade.php) to the user.The second function allows the user to get all the receipts for a particular email that a user enters.
Right now, if I directly go to the page where the user can enter the email and get all receipts, its working fine. But, if I try to do it through the process of adding a new user I get the error described in the title after I click submit to adding the user. However, it has added the user to the database, but shows the error because its sending the email which is the receipt.blade.php and has the undefined variable. My controller has these:
use App\Donor;
use App\Video;
use Illuminate\Http\Request;
use DB;
use Illuminate\Support\Facades\Input;
use Illuminate\Support\Facades\Mail;
The first Function is:
public function thankyoupage(Request $data, Mailer $mailer){
$donor = Donor::create([
'first_name'=> $data->first_name,
'last_name' => $data->last_name,
'email' => $data->email,
'video_count' => $video_count,
'amount_donated' => $data->amount_donated,
});
$mailer
->to($data->input('email'))
->send(new \App\Mail\MyMail(($data->input('first_name')),($data->input('last_name')),
($data->input('amount_donated'))));
return redirect()->action('PagesController#thank1');
}
My mailing file(mailable) is:
class MyMail extends Mailable
{
use Queueable, SerializesModels;
public $first_name, $last_name, $amount_donated;
/**
* Create a new message instance.
*
* #return void
*/
public function __construct($first_name,$last_name,$amount_donated)
{
$this->first_name = $first_name;
$this->last_name = $last_name;
$this->amount_donated = $amount_donated;
}
/**
* Build the message.
*
* #return $this
*/
public function build()
{
return $this->from('myemailid12341#gmail.com')
->view('emails.receipt');
}
}
The second function is:
public function getUserReceipts(Request $data){
$email = $data->email;
$donor = Donor::where('email', $email)->get();
return view('emails.receipt')->with(compact('donor'));
}
The receipt file simply contains:
#foreach($donor as $value)
{{ $value->first_name }}
{{ $value->last_name }}
{{ $value->amount_donated }}
#endforeach
The error I'm getting seems to be because donor in the receipt file is
undefined and I'm not sure how to fix it as it was passed with compact
in the second function.
Would really appreciate the help.
From your last question i can see the problem. The issue is that you're reusing the same view which takes two different set of data. When used with the method getUserReceipts, it generates receipts for multiple donors. But when you send the mail you're using the view as the mail content. In that case you need to use the properites set in the mailable class. The ideal case would be to create a new view for sending a single email receipt and handle it like so.
Change your mailable class to this. With this you can use all the properties of the donor in your view.
<?php
namespace App\Mail;
use App\Donor;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Queue\ShouldQueue;
class MyMail extends Mailable
{
use Queueable, SerializesModels;
public $donor;
public function __construct(Donor $donor)
{
$this->donor = $donor;
}
public function build()
{
return $this->from('myemailid12341#gmail.com')
->view('emails.singlereceipt');
}
}
Change the way you send the email to
\Mail::to($data->input('email'))->send(new \App\Mail\MyMail($donor));
Create a new view singlereceipt.blade.php and use the following code instead.
{{ $donor->first_name }}
{{ $donor->last_name }}
{{ $donor->amount_donated }}
You have an error in sending data to view. If you send data using with() function than syntax should be:
return view('emails.receipt')->with('donor', $donor);
See Passing Data To Views
Is there a way to add default headers to all emails in Laravel 5.1? I want all emails to be sent with the following header:
x-mailgun-native-send: true
Laravel uses SwiftMailer for mail sending.
When you use Mail facade to send an email, you call send() method and define a callback:
\Mail::send('emails.reminder', ['user' => $user], function ($m) use ($user) {
$m->to($user->email, $user->name)->subject('Your Reminder!');
});
Callback receives $m variable that is an \Illuminate\Mail\Message object, that has getSwiftMessage() method that returns \Swift_Message object which you can use to set headers:
$swiftMessage = $m->getSwiftMessage();
$headers = $swiftMessage->getHeaders();
$headers->addTextHeader('x-mailgun-native-send', 'true');
I know this is an old post, but I'm passing by the same problem right now and I think this could be useful to others.
If you're using a structure exactly as shown in Laravel (6.x and 7.x), like this:
/**
* Build the message.
*
* #return $this
*/
public function build()
{
return $this->from('example#example.com')
->view('emails.orders.shipped');
}
you could add headers to the E-mail in the format below:
public function build()
{
return $this->from('example#example.com')
->view('emails.orders.shipped')
->withSwiftMessage(function ($message) {
$message->getHeaders()
->addTextHeader('Custom-Header', 'HeaderValue');
});
}
I hope this could be useful.
link to the current documentation:https://laravel.com/docs/7.x/mail#customizing-the-swiftmailer-message
Slight modification to #maxim-lanin's answer. You can use it like this, fluently.
\Mail::send('email.view', ['user' => $user], function ($message) use ($user) {
$message->to($user->email, $user->name)
->subject('your message')
->getSwiftMessage()
->getHeaders()
->addTextHeader('x-mailgun-native-send', 'true');
});
For those who need to add this configuration to all emails (like me), there is a practical way using listeners.
Create a Listener:
php artisan make:listener MessageSendingListener
Add the following content to the MessageSendingListener:
<?php
namespace App\Listeners;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Events\MessageSending;
use Illuminate\Queue\InteractsWithQueue;
class MessageSendingListener
{
/**
* Handle the event.
*
* #param \Illuminate\Mail\Events\MessageSending $event
* #return void
*/
public function handle(MessageSending $event)
{
$event->message
->getHeaders()
->addTextHeader('x-mailgun-native-send', 'true');
}
}
Add the configuration to the application's event mapping array in EventServiceProvider:
<?php
namespace App\Providers;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
class EventServiceProvider extends ServiceProvider
{
/**
* The event listener mappings for the application.
*
* #var array
*/
protected $listen = [
// ...
\Illuminate\Mail\Events\MessageSending::class => [
\App\Listeners\MessageSendingListener::class,
],
];
}
Obs.: Tested with Laravel 7.