9.点赞/收藏功能

未匹配的标注

本系列文章为laracasts.com 的系列视频教程——Testing Laravel 的学习笔记。若喜欢该系列视频,可去该网站订阅后下载该系列视频,支持正版

本节说明

  • 对应第 9 小节:Liking A Model With TDD

本节内容

在一个 Laravel 应用中,对一个模型进行点赞或者是收藏是很常见的功能,例如我们可以点赞某个评论,收藏某篇文章。本节我们就来学习如何用 TDD 来开发这一功能。首先我们来对评论进行点赞,我们需要建立Post模型:

php artisan make:model Post -m

修改迁移文件:

database/migrations/{timestamp}_create_posts_table.php

<?php

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

class CreatePostsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->increments('id');
            $table->unsignedInteger('user_id');
            $table->string('title');
            $table->text('body');
            $table->timestamps();
        });
    }

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

然后我们来编写我们的第一个测试的思路:

tests/Unit/it/LikesTest.php

<?php

namespace Tests\Unit;

use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;

class LikesTest extends TestCase
{
    use RefreshDatabase;

    /** @test */
    public function a_user_can_like_a_post()
    {
        // given we have a post
        // and a logged user

        // when the user like a post

        // then we should see evidence in the database, and the post should be liked
    }
}

我们按照思路来填充代码:

    .
    .
    /** @test */
    public function a_user_can_like_a_post()
    {
        // given we have a post
        $post = factory('App\Post')->create();

        // and a logged user
        $user = factory('App\User')->create();
        $this->actingAs($user);

        // when the user like a post
        $post->like();

        // then we should see evidence in the database, and the post should be liked
        $this->assertDatabaseHas('likes',[
           'user_id' => $user->id,
           'likeable_id' => $post->id,
           'likeable_type' => get_class($post),
        ]);
    }

然后运行测试:
file
添加like()方法:

app/Post.php

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    public function like()
    {

    }
}

再次测试:
file
在我们的设定中,点赞这一行为不仅可以发生评论模型中,还可以发生在其他模型上,所以它与其他模型是 多态关联 的。所以我们新建Like模型并修改迁移文件如下:

database\migrations{timestamp}_create_likes_table.php

<?php

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

class CreateLikesTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('likes', function (Blueprint $table) {
            $table->integer('user_id')->index();
            $table->unsignedInteger('likeable_id');
            $table->string('likeable_type');

            $table->primary(['user_id','likeable_id','likeable_type']);
            $table->timestamps();
        });
    }

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

再次运行测试:
file
我们没有在数据库存入数据,这就是我们接下来需要做的工作:

app\Post.php
再次测试:
file
仍旧是批量赋值错误:

app\Like.php

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Like extends Model
{
    protected $fillable = ['user_id'];
}

再次测试:
file
我们再来补充一下我们的测试:断言该评论是isLiked。如下:

tests\Unit\LikesTest.php

    .
    .
    /** @test */
    public function a_user_can_like_a_post()
    {
       .
       .

        $this->assertTrue($post->isLiked());
    }
}

然后添加isLiked方法:

app\Post.php

    .
    .
    public function isLiked()
    {
        return $this->likes()
                    ->where(['user_id' => Auth::id()])
                    ->exists();
    }
}

运行测试:
file
现在我们已经完成了点赞评论功能的开发,相应地,我们来开发取消点赞功能的开发。我们仍旧从测试开始:

tests\Unit\LikesTest.php

    .
    .
    /** @test */
    public function a_user_can_unlike_a_post()
    {
        $post = factory('App\Post')->create();
        $user = factory('App\User')->create();
        $this->actingAs($user);

        $post->like();
        $post->unlike();

        $this->assertDatabaseMissing('likes',[
           'user_id' => $user->id,
           'likeable_id' => $post->id,
           'likeable_type' => get_class($post),
        ]);

        $this->assertFalse($post->isLiked());
    }
}

然后运行测试:
file
添加unlike方法:

app\Post.php

    .
    .
    public function like()
    {
        $like = new Like([
            'user_id' => Auth::id()
        ]);

        $this->likes()->save($like);
    }

    public function unlike()
    {
        $this->likes()
            ->where(['user_id' => Auth::id()])
            ->delete();
    }
    .
    .

再次测试:
file
继续前进,我们增加一个新的测试:增加一个toggle方法,当前评论未被点赞时,点赞该话题;已被点赞时,取消点赞该话题。添加测试:

tests\Unit\LikesTest.php

    .
    .
    /** @test */
    public function a_user_may_toggle_a_posts_like_status()
    {
        $post = factory('App\Post')->create();

        $user = factory('App\User')->create();
        $this->actingAs($user);

        $post->toggle();
        $this->assertTrue($post->isLiked());

        $post->toggle();
        $this->assertFalse($post->isLiked());
    }
}

运行测试:
file
添加toggle方法:

app\Post.php

    .
    .
    public function toggle()
    {
        if($this->isLiked()){
            return $this->unlike();
        }

        return $this->like();
    }
}

再次测试:
file
接下来我们再添加一个测试:获取点赞该评论的总数。

tests\Unit\LikesTest.php

    .
    .
    /** @test */
    public function a_post_knows_how_many_likes_it_has()
    {
        $post = factory('App\Post')->create();

        $user = factory('App\User')->create();
        $this->actingAs($user);

        $post->toggle();
        $this->assertEquals(1,$post->likesCount);
    }
}

获取likesCount

app\Post.php

    .
    .
    public function getLikesCountAttribute()
    {
        return $this->likes()->count();
    }
}

注:我们使用了 模型访问器 来获取likesCount

运行测试:
file
代码开发暂时完成,自然我们就可以来做点重构了。在文章开头说过,点赞一个模型是很常见的功能,并且我们的模型也是应用的是多态关联的。所以我们对点赞的行为的代码是可复用的,我们将其抽取成Trait

app\Likeability.php

<?php

namespace App;

use Auth;

trait Likeability 
{
    public function likes()
    {
        return $this->morphMany(Like::class,'likeable');
    }

    public function like()
    {
        $like = new Like([
            'user_id' => Auth::id()
        ]);

        $this->likes()->save($like);
    }

    public function unlike()
    {
        $this->likes()
            ->where(['user_id' => Auth::id()])
            ->delete();
    }

    public function isLiked()
    {
        return $this->likes()
                    ->where(['user_id' => Auth::id()])
                    ->exists();
    }

    public function toggle()
    {
        if($this->isLiked()){
            return $this->unlike();
        }

        return $this->like();
    }

    public function getLikesCountAttribute()
    {
        return $this->likes()->count();
    }
}

然后我们使用Trait

app\Post.php

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    use Likeability;

}

然后再次测试:
file
重构很成功,但是我们的测试也是可以重构的,我们将在下一节对我们的测试进行些重构。不如你先试试?

本文章首发在 LearnKu.com 网站上。

上一篇 下一篇
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
贡献者:2
讨论数量: 0
发起讨论 只看当前版本


暂无话题~