Twitter From Scratch Using Laravel: Create Tweet with Laravel Dusk


In our previous lesson we've learned about how to give a user the ability to create tweet. In this lesson we're going to learn how to automate browser test for our create tweet feature using Laravel Dusk.

First, let's install Laravel Dusk:
composer require laravel/dusk

Next, let's register it in our app/Providers/AppServiceProvider.php within the register method:
use Laravel\Dusk\DuskServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        if ($this->app->environment('local', 'testing')) {
            $this->app->register(DuskServiceProvider::class);
        }
    }
}

Next, let's run dusk:install artisan command:
php artisan dusk:install
This will create a Browser directory under our tests directory.

Next, let's create .env.dusk.local file in our root directory to force Dusk to use its own environment file when running tests.

Next, make sure to set your APP_URL environment variable in your .env.dusk.local file. The value should match the URL you use to access your application in a browser. In my case I use:
APP_URL=http://twitter.dev

To run our browser tests we need to use dusk artisan command:
php artisan dusk

Next, let's create a user test:
php artisan dusk:make UserTest
class UserTest extends DuskTestCase
{
    use DatabaseMigrations;

    /** @test */
    public function a_logged_in_user_can_create_a_tweet()
    {
        $this->browse(function (Browser $browser) {
            $user = factory('App\User')->create();
            $tweet = 'My first tweet';

            $browser->loginAs($user)
                    ->visit('/')
                    ->type('body', $tweet)
                    ->press('Tweet');

            $this->assertDatabaseHas('tweets', ['body' => $tweet]);
        });
    }
}
What we're doing here is we create a user using the factory and log it in using the loginAs method then visit the home page then simulate the typing of the textarea with a name of body and pressing the tweet button, then assert that the tweet is saved in the tweets table.

php artisan dusk

There was 1 error:

1) Tests\Browser\UserTest::a_logged_in_user_can_create_a_tweet
Swift_TransportException: Expected response code 250 but got code "530", with message "530 5.7.1 Authentication required"
This is because in our email verification lesson we opt-in to the created event of the User model. Therefore, every time we create a new user an email will be send.

Since in my end I don't have yet a mail setup, I'll just use log for the meantime and it's more practical in testing. So, in my .env.dusk.local file:
MAIL_DRIVER=log

php artisan dusk

There was 1 error:

1) Tests\Browser\UserTest::a_logged_in_user_can_create_a_tweet
Facebook\WebDriver\Exception\NoSuchElementException: no such element: Unable to locate element

Next, let's update our routes/web.php and remove /home:
Route::get('/', 'HomeController@index');
So, basically our root url will have two different displays depending if the user is authenticated or not.

Next, let's remove the constructor and update the index method in our HomeController.php:
class HomeController extends Controller
{
    /**
     * Show the application dashboard.
     *
     * @param \Illuminate\Http\Request $request
     * @return \Illuminate\Http\Response
     */
    public function index(Request $request)
    {
        if (is_null($request->user())) {
            return view('welcome');
        }

        return view('home');
    }
}
If the user is authenticated let's display the home view otherwise let's display the welcome view.

Next, let's update the $redirectTo property in both LoginController.php and RegisterController.php:
protected $redirectTo = '/';

Next, let's add a create tweet form in our home.blade.php:
<form action="{{ route('tweet.store', ['username' => $loggedUser->username]) }}" method="POST">
    {{ csrf_field() }}

    <div class="form-group">
        <textarea name="body" class="form-control" rows="3"></textarea>
    </div>
    <div class="text-right">
        <button type="submit" class="btn btn-primary">Tweet</button>
    </div>
</form>

Next, let's make that $loggedUser variable available in all our views by updating our Controller.php:
use App\User;
use Illuminate\Support\Facades\View;

class Controller extends BaseController
{
    use AuthorizesRequests, DispatchesJobs, ValidatesRequests;

    /**
     *  Create a new controller instance.
     */
    public function __construct()
    {
        $this->middleware(function ($request, $next) {
            $user = auth()->user();

            View::share('loggedIn', $user ? true : false);
            View::share('loggedUser', $user ?? new User);

            return $next($request);
        });
        
    }
}
We also add a global $loggedIn variable that can be useful when checking if the user is logged in. This time when we run our test again we should get green.

That's all for this lesson. If you have any question please write it down in the comment below and I will try to answer them in the best of my ability. See you in the next lesson. Thanks!

View the source code for this lesson on GitHub.

Previous: Create Tweet