Menu

9.话题与频道进行绑定

本节说明

  • 对应视频第 9 小节:A Thread Should Be Assigned A Channel

本节内容

在目前我们对新建thread的测试分成了两个:
guests_may_not_see_the_create_thread_pageguests_may_not_create_threads
我们可以合并成一个:对于未登录用户来说,无论是访问创建的页面或者进行创建的提交,我们都重定向到登录页面:
\tests\Feature\CreateThreadsTest.php

<?php

namespace Tests\Feature;

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('/login');

        $this->post('/threads')
            ->assertRedirect('/login');
    }

    /** @test */
    public function an_authenticated_user_can_create_new_forum_threads()
    {
        // Given we have a signed in user
        $this->signIn(create('App\User'));  // 已登录用户

        // When we hit the endpoint to cteate a new thread
        $thread = make('App\Thread');
        $this->post('/threads',$thread->toArray());

        // Then,when we visit the thread
        // We should see the new thread
        $this->get($thread->path())
            ->assertSee($thread->title)
            ->assertSee($thread->body);
    }
}

运行一下测试:
file
本项目中,我们认为,一个Thread属于一个Channel。现在让我们来引入Channel的概念。
首先建立测试:
forum\tests\Unit\ThreadTest.php

.
.
/** @test */
function a_thread_belongs_to_a_channel()
{
    $thread = create('App\Thread');

    $this->assertInstanceOf('App\Channel',$thread->channel);
}

运行测试:
file
因为我们还没有建立Channel的概念。新建Channel

$ php artisan make:model Channel -m

进行模型关联:
forum\app\Thread.php

.
.
public function creator()
{
    return $this->belongsTo(User::class,'user_id'); // 使用 user_id 字段进行模型关联
}

public function channel()
{
    return $this->belongsTo(Channel::class);
}
.
.

修改迁移文件:
forum\database\migrations{timestamp}_create_threads_table.php

.
.
public function up()
{
    Schema::create('threads', function (Blueprint $table) {
        $table->increments('id');
        $table->unsignedInteger('user_id');
        $table->unsignedInteger('channel_id');
        $table->string('title');
        $table->text('body');
        $table->timestamps();
    });
}
.
.

forum\database\migrations{timestamp}_create_channels_table.php

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateChannelsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('channels', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name',50);
            $table->string('slug',50);
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('channels');
    }
}

修改模型工厂:
forum\database\factories\ModelFactory.php

.
.
$factory->define(App\Thread::class,function ($faker){
   return [
       'user_id' => function () {
            return factory('App\User')->create()->id;
       },
       'channel_id' => function () {
            return factory('App\Channel')->create()->id;
       },
       'title' => $faker->sentence,
       'body' => $faker->paragraph,
    ];
});

$factory->define(App\Channel::class,function ($faker){
    $name = $faker->word;

    return [
        'name' => $name,
        'slug' => $name,
    ];
});
.
.

再次运行测试:

$ APP_ENV=testing phpunit --filter a_thread_belongs_to_a_channel

成功通过:
file

但是当我们运行完整测试时:

$ APP_ENV=testing phpunit

会失败:
file
我们需要修改一下ThreadsController.phpstore()方法:

.
.
public function store(Request $request)
{
    $thread = Thread::create([
        'user_id' => auth()->id(),
        'channel_id' => request('channel_id'),
        'title' => request('title'),
        'body' => request('body'),
    ]);

    return redirect($thread->path());
}
.
.

再次测试即可成功通过:
file
我们给Channel定义了slug字段,并且我们期望访问 http://forum.test/threads/1 时实际是访问并且显示 http://forum.test/threads/{channel}/1
现在来编写测试:
forum\tests\Unit\ThreadTest.php

.
.
public function setUp()
{
    parent::setUp(); // TODO: Change the autogenerated stub

    $this->thread = create('App\Thread');
}

/** @test */
function a_thread_can_make_a_string_path()
{
    $thread = create('App\Thread');

    $this->assertEquals("/threads/{$thread->channel->slug}/{$thread->id}",$thread->path());
}
.
.

运行测试:

$ APP_ENV=testing phpunit --filter a_thread_can_make_a_string_path

测试未通过:
file
修改path()方法:
forum\app\Thread.php

.
.
public function path()
{
    return "/threads/{$this->channel->slug}/{$this->id}";
}
.
.

再次测试即可通过:
file
我们的数据库发生了变化,需要重新运行迁移:

$ php artisan migrate:refresh

接着重新填充数据:

$ php artisan tinker

进入tinker环境后,执行以下命令进行数据填充:

>>> factory('App\Thread',50)->create();

为了更好地在路由中显示slug,我们修改web.php如下:

<?php

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/', function () {
    return view('welcome');
});

Auth::routes();

Route::get('/home', 'HomeController@index')->name('home');
Route::get('threads','ThreadsController@index');
Route::get('threads/create','ThreadsController@create');
Route::get('threads/{channel}/{thread}','ThreadsController@show');
Route::post('threads','ThreadsController@store');
Route::post('/threads/{thread}/replies','RepliesController@store');
//Route::resource('threads','ThreadsController');

修改show()方法:
forum\app\Http\Controllers\ThreadsController.php

.
.
public function show($channelId,Thread $thread)
{
    return view('threads.show',compact('thread'));
}
.
.

运行一下测试,看还有那些地方需要修改:

$ APP_ENV=testing phpunit

file
定位到an_authenticated_user_can_create_new_forum_threads方法:
forum\tests\Feature\CreateThreadsTest.php

.
.
/** @test */
public function an_authenticated_user_can_create_new_forum_threads()
{
    // Given we have a signed in user
    $this->signIn();  // 已登录用户

    // When we hit the endpoint to cteate a new thread
    $thread = make('App\Thread');
    $this->post('/threads',$thread->toArray());

    dd($thread->path()); // 打印出路径

    // Then,when we visit the thread
    // We should see the new thread
    $this->get($thread->path())
        ->assertSee($thread->title)
        ->assertSee($thread->body);
}
.
.

单独测试:

$ APP_ENV=testing phpunit --filter an_authenticated_user_can_create_new_forum_threads

file
而我们期望的$thread->path()应该是/threads/{channel}/{id},少了id。如果单独打印$thread->id的话,会发现它的值是null。究其原因,是因为我们使用了make()方法来获取模型实例,但并未将实例存入数据库中。我们需要使用create()

.
.
/** @test */
public function an_authenticated_user_can_create_new_forum_threads()
{
    $this->signIn();

    $thread = create('App\Thread');
    $this->post('/threads',$thread->toArray());

    $this->get($thread->path())
        ->assertSee($thread->title)
        ->assertSee($thread->body);
}
.
.

再次运行即可通过:
file
再次运行全部测试,看还有那些地方需要修改:

$ APP_ENV=testing phpunit

file
接着定位到an_authenticated_user_may_participate_in_forum_threads方法:
forum\tests\Feature\ParticipateInForumTest.php

.
.
/** @test */
function an_authenticated_user_may_participate_in_forum_threads()
{
    // Given we have a authenticated user
    $this->signIn();
    // And an existing thread
    $thread = create('App\Thread');

    // When the user adds a reply to the thread
    $reply = make('App\Reply');

    dd($thread->path() . '/replies');  // 打印出来

    $this->post($thread->path() .'/replies',$reply->toArray());

    // Then their reply should be visible on the page
    $this->get($thread->path())
        ->assertSee($reply->body);
}
.
.

单独测试:

$ APP_ENV=testing phpunit --filter an_authenticated_user_may_participate_in_forum_threads

file
会发现$thread->path()的形式为/threads/{channel}/{id}/replies。修复即可:
web.php

.
.
Route::post('/threads/{channel}/{thread}/replies','RepliesController@store');

forum\app\Http\Controllers\RepliesController.php

.
.
public function store($channelId,Thread $thread)
{
    $thread->addReply([
        'body' => request('body'),
        'user_id' => auth()->id(),
    ]);

    return back();
}
.
.

再次测试即可通过:
file

再次运行全部测试:

$ APP_ENV=testing phpunit

file
定位到unauthenticated_user_may_no_add_replies
forum\tests\Feature\ParticipateInForumTest.php

.
.
/** @test */
public function unauthenticated_user_may_no_add_replies()
{
    $this->expectException('Illuminate\Auth\AuthenticationException');

    $this->post('threads/1/replies',[]);
}
.
.

在这里,我们的测试逻辑是:未登录用户抛出异常。但是我们真正的测试逻辑应该是:未登录用户试图进行此动作,我们将其重定向至登录页面:

.
.
/** @test */
public function unauthenticated_user_may_no_add_replies()
{
    $this->withExceptionHandling()
        ->post('threads/some-channel/1/replies',[])
        ->assertRedirect('/login');
}
.
.

再次运行测试即可成功通过:
file
重新注册用户,然后登录即可看到效果:
file

本文章首发在 Laravel China 社区
上一篇 下一篇
讨论数量: 0
发起讨论


暂无话题~