DEV Community

Cover image for Creating a project template using Laravel, Vue 3, and Tailwind - Part 2
Nolan Nordlund
Nolan Nordlund

Posted on • Updated on

Creating a project template using Laravel, Vue 3, and Tailwind - Part 2

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
Enter fullscreen mode Exit fullscreen mode

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";
...
Enter fullscreen mode Exit fullscreen mode
// 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'),
    ]);
Enter fullscreen mode Exit fullscreen mode
// 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')],
};
Enter fullscreen mode Exit fullscreen mode

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>
...
Enter fullscreen mode Exit fullscreen mode

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>
...
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Testing with Jest

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
Enter fullscreen mode Exit fullscreen mode

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"
                    }
                }
            ]
        ]
    }
Enter fullscreen mode Exit fullscreen mode

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')
});
Enter fullscreen mode Exit fullscreen mode

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"
    },
...
Enter fullscreen mode Exit fullscreen mode

Let's see it in action.

npm run test
Enter fullscreen mode Exit fullscreen mode

Testing with Jest

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)

Collapse
 
anduru profile image
Andrey

how to solve the problem?

/var/www/html# npm run test

> test
> jest

 FAIL  tests/components/Home.test.js
   displays message (2 ms)

   displays message

    The error below may be caused by using the wrong test environment, see https://jestjs.io/docs/configuration#testenvironment-string.
    Consider using the "jsdom" test environment.

    ReferenceError: document is not defined

       7 |
       8 | test("displays message", () => {
    >  9 |     const wrapper = mount(Home);
         |                     ^
      10 |
      11 |     expect(wrapper.text()).toContain("Home component");
      12 | });

      at mount (node_modules/@vue/test-utils/dist/vue-test-utils.cjs.js:7840:14)
      at Object.<anonymous> (tests/components/Home.test.js:9:21)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 total
Snapshots:   0 total
Time:        4.516 s
Ran all test suites.

Enter fullscreen mode Exit fullscreen mode
Collapse
 
kakeru007 profile image
kakeru007

So on your package.json, replace:
{
...
"scripts": {
...
"test": "jest"
...
},
}

with

{
...
"scripts": {
...
"test": "jest --env=jsdom"
...
},
}

Collapse
 
6rucewayne profile image
6ruceWayne

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

TypeError: Cannot destructure property 'config' of 'undefined' as it is undefined.

  at Object.getCacheKey (node_modules/vue-jest/lib/index.js:10:7)
  at ScriptTransformer._getCacheKey (node_modules/@jest/transform/build/ScriptTransformer.js:280:41)
  at ScriptTransformer._getFileCachePath (node_modules/@jest/transform/build/ScriptTransformer.js:351:27)
  at ScriptTransformer.transformSource (node_modules/@jest/transform/build/ScriptTransformer.js:588:32)
  at ScriptTransformer._transformAndBuildScript (node_modules/@jest/transform/build/ScriptTransformer.js:758:40)
  at ScriptTransformer.transform (node_modules/@jest/transform/build/ScriptTransformer.js:815:19)
Enter fullscreen mode Exit fullscreen mode

I have no idea what it can mean

Collapse
 
0xaliraza profile image
Ali Raza

You need to remove vue-jest and use vue3-jest.

Collapse
 
0xaliraza profile image
Ali Raza

Please change vue-jest to vue3-jest because the current testing config give an error. Nice work, thanks!