73.邮箱认证(三)
- 本系列文章为
laracasts.com的系列视频教程——Let's Build A Forum with Laravel and TDD 的学习笔记。若喜欢该系列视频,可去该网站订阅后下载该系列视频, 支持正版 ;- 视频源码地址:github.com/laracasts/Lets-Build-a-...;
- 本项目为一个 forum(论坛)项目,与本站的第二本实战教程 《Laravel 教程 - Web 开发实战进阶》 类似,可互相参照。
本节说明
- 对应视频教程第 73 小节:Users Must Confirm Their Email Address: #3 - Cleanup
本节内容
上一节我们利用事件机制实现了注册用户时发送认证邮件的功能,但是只为了发送邮件就定义了事件,似乎有点杀鸡用牛刀。所以本节我们通过重写registered()方法很轻松地实现相同的功能:
forum\app\Http\Controllers\Auth\RegisterController.php
<?php
namespace App\Http\Controllers\Auth;
use App\Mail\PleaseConfirmYourEmail;
use App\User;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Validator;
use Illuminate\Foundation\Auth\RegistersUsers;
class RegisterController extends Controller
{
    use RegistersUsers;
    protected $redirectTo = '/home';
    public function __construct()
    {
        $this->middleware('guest');
    }
    protected function validator(array $data)
    {
        return Validator::make($data, [
            'name' => 'required|string|max:255',
            'email' => 'required|string|email|max:255|unique:users',
            'password' => 'required|string|min:6|confirmed',
        ]);
    }
    protected function create(array $data)
    {
        return User::forceCreate([
            'name' => $data['name'],
            'email' => $data['email'],
            'password' => bcrypt($data['password']),
            'confirmation_token' => str_random(25)
        ]);
    }
    protected function registered(Request $request, $user)
    {
        Mail::to($user)->send(new PleaseConfirmYourEmail($user));
        return redirect($this->redirectPath());
    }
}然后我们可以去掉事件的注册:
forum\app\Providers\EventServiceProvider.php
<?php
namespace App\Providers;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
class EventServiceProvider extends ServiceProvider
{
    protected $listen = [
        'App\Events\ThreadReceivedNewReply' => [
            'App\Listeners\NotifyMentionedUsers',
            'App\Listeners\NotifySubscribers',
        ]
    ];
    .
    .
}接着修改我们的测试:
tests\Feature\RegisrationTest.php
.
.
/** @test */
public function a_confirmation_email_is_sent_upon_registration()
{
    Mail::fake();
    // 用路由命名代替 url
    $this->post(route('register'),[
        'name' => 'NoNo1',
        'email' => 'NoNo1@example.com',
        'password' => '123456',
        'password_confirmation' => '123456'
    ]);
    Mail::assertSent(PleaseConfirmYourEmail::class);
}
.
.运行测试:
接下来我们来进行些重构,用路由命名代替url:
forum\tests\Feature\RegistrationTest.php
<?php
namespace Tests\Feature;
use App\Mail\PleaseConfirmYourEmail;
use App\User;
use Illuminate\Support\Facades\Mail;
use Tests\TestCase;
use Illuminate\Foundation\Testing\DatabaseMigrations;
class RegistrationTest extends TestCase
{
    use DatabaseMigrations;
    /** @test */
    public function a_confirmation_email_is_sent_upon_registration()
    {
        Mail::fake();
        // 用路由命名代替 url
        $this->post(route('register'),[
            'name' => 'NoNo1',
            'email' => 'NoNo1@example.com',
            'password' => '123456',
            'password_confirmation' => '123456'
        ]);
        Mail::assertSent(PleaseConfirmYourEmail::class);
    }
    /** @test */
    public function user_can_fully_confirm_their_email_addresses()
    {
        // 用路由命名代替 url
        $this->post(route('register'),[
            'name' => 'NoNo1',
            'email' => 'NoNo1@example.com',
            'password' => '123456',
            'password_confirmation' => '123456'
        ]);
        $user = User::whereName('NoNo1')->first();
        $this->assertFalse($user->confirmed);
        $this->assertNotNull($user->confirmation_token);
        // 用路由命名代替 url
        $this->get(route('register.confirm',['token' => $user->confirmation_token]))
            ->assertRedirect(route('threads'));
        $this->assertTrue($user->fresh()->confirmed);
    }
}我们来为路由加上命名:
forum\routes\web.php
.
.
Route::get('threads','ThreadsController@index')->name('threads');
.
.
Route::get('/register/confirm','Api\RegisterConfirmationController@index')->name('register.confirm');
.
.我们运行测试:
现在我们来运行全部测试:
以上的报错原因是因为我们默认一个新用户是未认证的,而我们对未认证用户做了限制发布话题的设置。对于我们的测试而言,我们大多数的功能测试的前提都是在用户已认证的情况下的,所以我们来对模型工厂文件进行一点修改:
forum\database\factories\ModelFactory.php
<?php
$factory->define(App\User::class, function (Faker\Generator $faker) {
    static $password;
    return [
        'name' => $faker->name,
        'email' => $faker->unique()->safeEmail,
        'password' => $password ?: $password = bcrypt('123456'),
        'remember_token' => str_random(10),
        'confirmed' => true,
    ];
});
$factory->state(App\User::class,'unconfirmed',function () {
   return [
      'confirmed' => false
   ];
});
.
.我们默认新用户是已认证状态,当我们需要测试未认证的相关逻辑时,我们调用unconfirmed即可。接下来我们对CreateThreadsTest.php进行清理,因改动处较多,说明放在了代码的注释中:
<?php
namespace Tests\Feature;
use App\Activity;
use Tests\TestCase;
use Illuminate\Foundation\Testing\DatabaseMigrations;
class CreateThreadsTest extends TestCase
{
    use DatabaseMigrations;
    /** @test */
    public function guests_may_not_create_threads()
    {
        $this->withExceptionHandling();
        $this->get('/threads/create')
            ->assertRedirect(route('login')); // 应用路由命名
        $this->post(route('threads')) // 应用路由命名
            ->assertRedirect(route('login')); // 应用路由命名
    }
    // 修改测试命名,更加辨识度
    /** @test */
    public function new_users_must_first_confirm_their_email_address_before_creating_threads()
    {
        // 调用 unconfirmed,生成未认证用户
        $user = factory('App\User')->states('unconfirmed')->create();
        $this->signIn($user);
        $thread = make('App\Thread');
        $this->post(route('threads'),$thread->toArray())
            ->assertRedirect('/threads')
            ->assertSessionHas('flash','You must first confirm your email address.');
    }
    // 修改测试命名,更加辨识度
    /** @test */
    public function a_user_can_create_new_forum_threads()
    {
        $this->signIn();
        $thread = make('App\Thread');
        $response = $this->post(route('threads'),$thread->toArray());// 应用路由命名
        $this->get($response->headers->get('Location'))
            ->assertSee($thread->title)
            ->assertSee($thread->body);
    }
    /** @test */
    public function a_thread_requires_a_title()
    {
        $this->publishThread(['title' => null])
            ->assertSessionHasErrors('title');
    }
    /** @test */
    public function a_thread_requires_a_body()
    {
        $this->publishThread(['body' => null])
            ->assertSessionHasErrors('body');
    }
    /** @test */
    public function a_thread_requires_a_valid_channel()
    {
        factory('App\Channel',2)->create(); // 新建两个 Channel,id 分别为 1 跟 2
        $this->publishThread(['channel_id' => null])
            ->assertSessionHasErrors('channel_id');
        $this->publishThread(['channel_id' => 999])  // channle_id 为 999,是一个不存在的 Channel
            ->assertSessionHasErrors('channel_id');
    }
    /** @test */
    public function unauthorized_users_may_not_delete_threads()
    {
        $this->withExceptionHandling();
        $thread = create('App\Thread');
        $this->delete($thread->path())->assertRedirect(route('login')); // 应用路由命名
        $this->signIn();
        $this->delete($thread->path())->assertStatus(403);
    }
    /** @test */
    public function authorized_users_can_delete_threads()
    {
        $this->signIn();
        $thread = create('App\Thread',['user_id' => auth()->id()]);
        $reply = create('App\Reply',['thread_id' => $thread->id]);
        $response =  $this->json('DELETE',$thread->path());
        $response->assertStatus(204);
        $this->assertDatabaseMissing('threads',['id' => $thread->id]);
        $this->assertDatabaseMissing('replies',['id' => $reply->id]);
        $this->assertEquals(0,Activity::count());
    }
    public function publishThread($overrides = [])
    {
        $this->withExceptionHandling()->signIn();
        $thread = make('App\Thread',$overrides);
        return $this->post(route('threads'),$thread->toArray()); // 应用路由命名
    }
}运行测试:
接下来我们新增一个测试:
forum\tests\Feature\RegistrationTest.php
    .
    .
    /** @test */
    public function user_can_fully_confirm_their_email_addresses()
    {
        // 应用 【邮件模拟】
        Mail::fake();
        $this->post(route('register'),[
            'name' => 'NoNo1',
            'email' => 'NoNo1@example.com',
            'password' => '123456',
            'password_confirmation' => '123456'
        ]);
        $user = User::whereName('NoNo1')->first();
        $this->assertFalse($user->confirmed);
        $this->assertNotNull($user->confirmation_token);
        $this->get(route('register.confirm',['token' => $user->confirmation_token]))
            ->assertRedirect(route('threads'));
        $this->assertTrue($user->fresh()->confirmed);
    }
    /** @test */
    public function confirming_an_invalid_token()
    {
        // 测试无效 Token
        $this->get(route('register.confirm'),['token' => 'invalid'])
            ->assertRedirect(route('threads'))
            ->assertSessionHas('flash','Unknown token.');
    }
}为了让测试通过,我们要修改控制器:
forum\app\Http\Controllers\Api\RegisterConfirmationController.php
    .
    .
    public function index()
    {
        try {
            User::where('confirmation_token',request('token'))
                ->firstOrFail()
                ->confirm();
        }catch (\Exception $e) {
            return redirect(route('threads'))
                ->with('flash','Unknown token.');
        }
        return redirect(route('threads'))
            ->with('flash','Your account is now confirmed! You may post to the forum.');
    }
    .
    .现在我们运行测试:
运行全部测试:
 
           TDD 构建 Laravel 论坛笔记
TDD 构建 Laravel 论坛笔记 
         
                     
                     
             
             关于 LearnKu
                关于 LearnKu
               
                     
                     
                     粤公网安备 44030502004330号
 粤公网安备 44030502004330号 
 
推荐文章: