This article was originally published at projectrebel.io.
Creating a project template using Laravel, Vue 3, and Tailwind
Today we are going to take our simple project template and bring it to the next level by adding Laravel Breeze for simple authentication and set up our TDD workflow using PHPUnit and Jest. If you missed the last post, I'd suggest checking it out. It'll only take a few minutes for you to catch up but if you can also download the source code on Github.
Installing Breeze
Just as it was when installing the Laravel framework, adding Breeze is incredibly simple. Just a few commands will add everything that we need.
composer require laravel/breeze --dev
php artisan breeze:install
npm install
npm run dev
The breeze:install
command will add a ton of files to your project structure. In the resources
folder, you'll see several views added that are specific to authentication. These are things like login, register, or password reset views. You'll have a new components
folder also. These are Laravel Components which are blade templates but with some additional horsepower that work together to make them more programmatic in nature. We won't go into detail on them here so if you want to learn more, take a look at the docs. We have a layouts
folder for our navigation as well as layouts for guests and authenticated users. Finally, dashboard.blade.php
will be added to the root level of our views
folder. This is the view that is returned when a user successfully login into the application. There are corresponding classes in app\View\Components
.
Several controllers have been added to app\Http\Controllers\Auth
as well as a LoginRequest
in app\Http\Requests\Auth
. We have new routes added via routes/auth.php
and in routes/web.php
. Our RouteServiceProvider
has also been updated to redirect users to /dashboard
once they authenticate. There are also tests for our authentication process but we'll get to those in a minute. Before we get to testing, we'll need to visit some files that Breeze overwrites and merge the new changes with our previous versions of the files.
In our package.json
, you'll see that some of the versions of packages that we already had installed might have changed. These aren't too important so I'll leave it up to you if you want to revert to our previous versions or not. What is important is that a few new packages have been added. @tailwindcss/forms
, alpinejs
, and postcss-import
are all needed by the Breeze starter kit. Making use of these new packages requires changes to app.js
, webpack.mix.js
, and tailwind.config.js
.
// resources/js/app.js
require("./bootstrap");
require('alpinejs');
import { createApp, h } from "vue";
...
// webpack.mix.js
...
mix.js('resources/js/app.js', 'public/js').vue();
mix.postCss('resources/css/app.css', 'public/css', [
require('postcss-import'),
require('tailwindcss'),
require('autoprefixer'),
]);
// tailwind.config.js
const defaultTheme = require('tailwindcss/defaultTheme');
module.exports = {
purge: ['./storage/framework/views/*.php', './resources/views/**/*.blade.php'],
darkMode: false, // or 'media' or 'class'
theme: {
extend: {
fontFamily: {
sans: ['Nunito', ...defaultTheme.fontFamily.sans],
},
},
},
variants: {
extend: {
opacity: ['disabled'],
},
},
plugins: [require('@tailwindcss/forms')],
};
The changes to the first two files are pretty simple. We are just adding alpinejs
to our main javascript file and including postcss-import
and autoprefixer
in our CSS build process. The tailwind.config.js
changes are a little bigger although the only one that is truly necessary is adding the official tailwind forms plugin to our configuration. The last change to mention is in resources/css/app.css
. Breeze updates this file to use an alternate syntax for including the tailwind base styles. I like the brevity of the @tailwind
syntax, but either option works.
Let's add links for the login and register pages to our welcome
view.
// resources/views/welcome.blade.php
...
<body>
@if (Route::has('login'))
<div class="hidden fixed top-0 right-0 px-6 py-4 sm:block">
@auth
<a href="{{ url('/dashboard') }}" class="text-sm text-gray-700 underline">Dashboard</a>
@else
<a href="{{ route('login') }}" class="text-sm text-gray-700 underline">Login</a>
@if (Route::has('register'))
<a href="{{ route('register') }}" class="ml-4 text-sm text-gray-700 underline">Register</a>
@endif
@endauth
</div>
@endif
<div id="app" class="flex items-center bg-gray-100 min-h-screen">
<home />
</div>
...
You should see the links in the top right of our welcome view. We'll want to register a new user but first, we need to create a database for our application. Once it is set up, run php artisan migrate
and then we can register our user. After registering you should be redirected to the dashboard which was created by Breeze. Our authentication system is in place!
Configuring PHPUnit
Laravel has some great tools which extend the functionality of PHPUnit making TDD an excellent fit for any Laravel project. We'll just make a couple of extra config changes before we move onto the javascript portion of our testing setup. In our phpunit.xml
file, let's uncomment the 2 entries within the PHP section so our tests will use sqlite in memory when testing.
// phpunit.xml
...
<php>
<server name="APP_ENV" value="testing"/>
<server name="BCRYPT_ROUNDS" value="4"/>
<server name="CACHE_DRIVER" value="array"/>
<server name="DB_CONNECTION" value="sqlite"/>
<server name="DB_DATABASE" value=":memory:"/>
<server name="MAIL_MAILER" value="array"/>
<server name="QUEUE_CONNECTION" value="sync"/>
<server name="SESSION_DRIVER" value="array"/>
<server name="TELESCOPE_ENABLED" value="false"/>
</php>
...
You will be able to run the tests using vendor/bin/phpunit
but Laravel has an awesome feature called parallel testing which greatly decreases the testing time of large test suites by running tests on multiple cores. There might not be a significant improvement at this point but we'll add it regardless. Running php artisan test --parallel
will prompt you to install brianium/paratest
. Once it is installed, the same command will run the test suite for us.
php artisan test --parallel
Javascript testing via Jest
The last goal we want to accomplish in this version of our project template is having automated testing capabilities for our frontend. There are lots of ways to do this in the javascript ecosystem. We will use Jest as our main testing tool and Vue Test Utils to allow testing of our Vue single file components (SFCs).
We can add all the packages we need in one go by running the following command.
npm install --save-dev jest vue-jest@~5.0.0-alpha babel-jest @babel/core @babel/preset-env @vue/test-utils@next
We need some extra configuration for Jest and Babel to get these packages working together to provide us a usable testing environment. You can either add new config files specific to each package in your root directory (jest.config.js
and babel.config.js
) or just add the relevant sections to package.json
.
// package.json
...
"jest": {
"testRegex": "tests/components/.*.test.js$",
"moduleFileExtensions": [
"js",
"vue"
],
"moduleNameMapper": {
"^@/(.*)$": "<rootDir>/resources/js/$1"
},
"transform": {
"^.+\\.js$": "<rootDir>/node_modules/babel-jest",
".*\\.vue$": "<rootDir>/node_modules/vue-jest"
}
},
"babel": {
"presets": [
[
"@babel/preset-env",
{
"targets": {
"node": "current"
}
}
]
]
}
At the very beginning of our Jest configuration, we have an entry for testRegex
. This is the pattern that Jest will use to find our test files. I am going to add a new folder called components
inside of the tests
folder. Now Jest should find any files in this folder that end with test.js
. Another common standard to use is spec.js
. Let's add a simple test for our Home
component.
// tests/components/Home.test.js
import Home from '../../resources/js/components/Home.vue';
import { mount } from '@vue/test-utils'
test('displays message', () => {
const wrapper = mount(Home);
expect(wrapper.text()).toContain('Home component')
});
The last step is to add scripts to run Jest against our test suite.
// package.json
...
"scripts": {
"dev": "npm run development",
"development": "mix",
"watch": "mix watch",
"watch-poll": "mix watch -- --watch-options-poll=1000",
"hot": "mix watch --hot",
"prod": "npm run production",
"production": "mix --production",
"test": "jest",
"testing": "jest --watch"
},
...
Let's see it in action.
npm run test
Conclusion and next steps
While the basic version of our project template is helpful to simplify the start-up process of the most basic experiments, this version with authentication and a working test driven development workflow is much more practical for a real project. Therefore, we are saving much more time getting up and running with a template like this one. You can find the working template on Github once again. Next time we are going to set up a single page application template for projects that need more interactivity with the user.
Top comments (5)
how to solve the problem?
So on your package.json, replace:
{
...
"scripts": {
...
"test": "jest"
...
},
}
with
{
...
"scripts": {
...
"test": "jest --env=jsdom"
...
},
}
Hi! Could you help me please? After I run command "npm run test" I get following log:
FAIL tests/components/Home.test.js
? Test suite failed to run
I have no idea what it can mean
You need to remove vue-jest and use vue3-jest.
Please change
vue-jest
tovue3-jest
because the current testing config give an error. Nice work, thanks!