---
title: 'Laravel Testing Crash Course: From Zero to Pest v4'
source: 'https://youtube.com/watch?v=qfN-rJ-K5WI'
video_id: 'qfN-rJ-K5WI'
date: 2026-06-15
duration_sec: 0
---

# Laravel Testing Crash Course: From Zero to Pest v4

> Source: [Laravel Testing Crash Course: From Zero to Pest v4](https://youtube.com/watch?v=qfN-rJ-K5WI)

## Summary

This course teaches how to write fast, reliable, and maintainable tests for Laravel applications using the Pest framework. It covers unit tests, feature tests, browser tests, and deployment with CI/CD. The course is hands-on, starting with basics and progressing to testing a real project.

### Key Points

- **What is Software Testing** [02:54] — Software testing is a systematic process of verifying that your application works correctly and behaves as expected. It ensures features continue to work after months of development, major refactoring, or team changes.
- **Three Main Reasons for Testing** [03:13] — 1) Verify code performs as designed under various conditions. 2) Catch bugs during development before they reach users. 3) Make changes confidently knowing tests will alert you to unintended consequences.
- **Four Types of Automated Tests** [04:39] — Unit tests (test isolated logic, fast, no database/HTTP), feature tests (test user-facing workflows including routes, controllers, models, database), integration tests (verify multiple systems work together), and browser tests (simulate actual user behavior in a real browser).
- **Manual vs Automated Testing** [05:40] — Manual testing is time-intensive, error-prone, and requires repetitive work. Automated testing is lightning fast, perfectly consistent, provides comprehensive coverage, and enables continuous validation in CI/CD pipelines.
- **Characteristics of a Good Test** [07:08] — A good test is clear and readable, focused on one thing, deterministic (same result every time), fast, and meaningful (test cases should give good understanding of what the project does).
- **Pest vs PHPUnit** [09:10] — PHPUnit is the core testing framework. Pest is built on top of PHPUnit and provides a more elegant and nicer syntax. Pest 4 has additional great features. Pest uses expectations API instead of assertions API, but both are supported.
- **Creating Unit and Feature Tests** [12:25] — Use `php artisan make:test TestName` to create a feature test (default). Use `--unit` flag to create a unit test. Feature tests test HTTP requests and responses; unit tests test isolated logic without HTTP or database dependencies.
- **Test Configuration** [19:18] — Tests use `phpunit.xml` for environment variables. Default DB connection is SQLite in-memory. You can create `.env.testing` file for test-specific environment variables. `phpunit.xml` also allows customizing test directories.
- **Pest Configuration File (pest.php)** [21:44] — This file configures default browser, global timeout, user agent, and custom expectations. It also sets up global hooks (beforeEach, beforeAll, afterEach, afterAll) that run for every test or test file.
- **Expectations API** [24:24] — Pest replaces assertions with expectations for more readable code. Examples: `expect(5)->toBe(5)`, `expect('Zura')->toBeString()->toContain('Z')`. Expectations can be chained and negated with `not`. Common expectations: `toBeArray`, `toHaveCount`, `toBeInstanceOf`.
- **Writing Unit Tests for a Service Class** [31:34] — Unit tests should not use HTTP, external services, or databases. They test pure logic of a class or method. Example: testing a PriceCalculator service with methods like `applyPercentageDiscount`, `applyFixedDiscount`, `addTax`, and `finalPrice`.
- **Grouping Tests with describe()** [39:58] — Use `describe('group name', function () { ... })` to group related tests. This organizes tests into logical groups and makes output cleaner. Example: `describe('price calculator percentage discount', function () { ... })`.
- **Browser Testing with Pest** [55:28] — Browser testing is essential for client-side rendered applications (e.g., Inertia/React). Install `pest-plugin-browser` and Playwright. Use `visit()` instead of `get()`. Methods: `click()`, `type()`, `press()`, `assertSee()`, `assertPathIs()`, `screenshot()`, `debug()`.
- **Browser Testing Assertions** [66:35] — Assertions include: `assertTitle()`, `assertChecked()`, `assertSelected()`, `assertAttribute()`, `assertEnabled()`, `assertUrlIs()`, `assertElementsCount()`, `assertScreenshotMatches()`. These allow comprehensive UI testing.
- **Testing Mobile Menu with Device Emulation** [69:53] — Use `->mobile()` method to emulate mobile viewport. Then interact with elements (e.g., click sidebar toggle) and assert visibility of expected content. This ensures responsive design works correctly.
- **Testing Console Logs and JavaScript Errors** [70:00] — Use `assertNoConsoleLogs()` and `assertNoJavaScriptErrors()` to verify that pages have no console errors. This is useful for catching JavaScript issues during testing.
- **Testing Database and Models** [76:10] — Use `RefreshDatabase` trait to reset database between tests. Test model attributes, relationships, scopes, CRUD operations, and soft deletes. Use factories to create test data. Assert database state with `assertDatabaseHas()` and `assertDatabaseMissing()`.
- **Hooks: Local and Global** [83:03] — Local hooks (`beforeEach`, `beforeAll`, `afterEach`, `afterAll`) are defined per test file. Global hooks are defined in `pest.php`. Execution order: global beforeEach → local beforeEach → test → local afterEach → global afterEach.
- **Data Sets and Data Providers** [86:00] — Use `with()` to pass data sets to tests. Data can be arrays or named data sets. Tests run once per data set element. Supports closures for dynamic data. Example: `test('sum', function ($a, $b, $c) { ... })->with([[1,2,3], [4,5,9]])`.
- **Snapshot Testing** [92:12] — Snapshot testing captures the response content and compares it to a stored snapshot. Use `expect($response->content())->toMatchSnapshot()`. Ideal for server-side rendered pages. Snapshots are stored in `tests/.pest/snapshots/`.
- **Visual Regression Testing with Screenshots** [95:01] — Use `assertScreenshotMatches()` to compare current page screenshot with a baseline. If changes are intentional, update snapshots with `--update-snapshots` flag. Provides diff HTML for visual comparison.
- **Grouping and Filtering Tests** [98:10] — Assign groups with `->group('name')` and run specific groups with `php artisan test --group=name`. Filter by test name with `--filter=keyword`. Skip tests with `->skip()` or mark as todo with `->todo()`.
- **Testing APIs** [103:12] — Use `postJson()`, `getJson()` for API requests. Assert JSON structure with `assertJsonStructure()`, exact JSON with `assertJson()`, and validation errors. Test rate limiting, authentication, and response data structure.
- **Real Project Testing Example** [108:02] — A Laravel Medium clone project is used to demonstrate real-world testing. Tests include user registration, login, post creation, editing, deleting, following/unfollowing users, clapping posts, and dashboard display.
- **Browser Test for Dashboard Posts** [142:18] — A browser test verifies that the dashboard shows posts from followed users and hides posts from unfollowed users. It uses login, navigation, and assertions to check visibility of specific post titles.
- **Deployment with CI/CD** [150:22] — The project is deployed to Hostinger shared hosting using an open-source package. CI/CD is set up with GitHub Actions: tests run on push, and deployment only occurs if all tests pass. The workflow includes building assets and running browser tests.

### Conclusion

This course provides a comprehensive guide to testing Laravel applications with Pest v4, covering everything from basic unit tests to advanced browser testing and CI/CD deployment. By the end, you'll have the skills to write robust tests and automate your deployment pipeline.

## Transcript

Hello everyone and welcome to my Laravel
testing course. In this tutorial, you're
going to learn how to write fast,
reliable and maintainable tests for your
Laravel applications using pest
framework. This course is fully
hands-on. We will start with several
slides and understand what is testing,
why it is necessary or what type of
tests there exist. After this, we're
going to create larl project and write
our first tests. In this tutorial,
you're going to learn how to write unit
tests, feature tests, as well as browser
tests, how to test authentication part,
databases, models, what are data sets,
and how to configure hooks. We're going
to get familiar with test configuration.
We're going to also learn how to write
snapshot and visual regression tests
where we take the screenshots and
compare it to the previous stable
version. We're going to have a look at
the device emulation as well and we're
going to write tests for APIs as well.
We're going to learn how to filter,
group, and structure our tests. In a
nutshell, we will learn all the
fundamental things you need to know to
get started with testing. And finally,
we will take a real project and create
real tests for this project. And at the
end of this tutorial, we're going to use
continuous integration and deploy our
project on a production ready
environment where whenever you push
something, our written tests are
executed and the deployment will only
happen if all tests are passed. All code
written in this tutorial is open source
and the necessary links will be
available in the video description.
Before we start, I want to ask you to
hit the like and subscribe because that
really helps me to keep creating such
type of content. At the end, as I
mentioned, we're going to deploy this
project on production environment,
assign custom domain to it, and set up
automatic deployment where tests are
running. As a choice of the hosting
provider, we're going to use Hostinger's
shared hosting, which is also sponsoring
this video. In my opinion, Hostinger's
shared hosting is the best and the most
affordable hosting option out there. Go
to the website
hostinger.com/thecodeolic.
They always have some sort of discount
which gives you ability to get the
hosting as low as $3 per month which is
ridiculously low compared to what value
you're going to get with that price.
Each hosting option gives you ability to
create more than one website. You have
domain for one year and you have bunch
of other advantages. Choose whichever
option is the best for you. Then you can
adjust your business web hosting period.
The most reasonable is probably one
year, but the biggest discount you're
going to get if you choose biggest
period. If you decide to get the
hosting, use a coupon code the code
holik and you will get extra 10% off.
Now, let's focus on the course and at
the end we're going to use this hosting
to deploy our project on custom domain
on production environment and we're
going to set up automatic deployment as
well where tests are running before they
are deployed to the server. First, let's
understand what is software testing.
Software testing is a systematic process
of verifying that your application works
correctly and behaves the way you expect
it to behave. Testing ensures that
features continue to work as intended
even after months of development, major
refactoring or team changes. Here are
three main reasons why testing is
necessary. To verify your code performs
exactly as designed under various
conditions and edge cases. To catch bugs
during development before they even
reach to your users and to make changes
confidently knowing tests will alert you
to any unintended consequences. Let's
make this specific to Laravel
developers. In Laravel, when you have
tests written for your project and you
make some changes, it automatically can
detect that new code breaks some
existing functionality and eliminate
that and report this to you. It can save
your development time knowing that you
have something which already tests your
application. You don't need to test it.
It runs the entire test suite instantly
instead of manually testing features
repeatedly. It gives you ability to
safely refactor the code of your
project. Knowing if something is broken,
test will catch it. It gives you ability
to build and deploy without a fear.
Knowing your comprehensive test coverage
has your back. And finally, everything
comes down to deliver your project in
high quality to ship features that works
in various cases and that keeps working
over time when new features are added.
There are four types of automated tests.
Unit tests which has the main purpose to
test isolated logic of your project.
Typically, unit tests test a specific
method or specific class. It is fast and
focused on no database, no HTTP
dependencies, just plain logic. We have
feature tests which test the entire
userfacing workflows including roads,
controllers middlewares models
database interactions and views. These
simulate real application behavior. We
have also integration tests to verify
how multiple systems work together, how
they interact, how they share data. And
we have browser tests which simulate how
actual user behaves in your actual real
browser such as clicking on the buttons,
typing form submissions and complex UI
workflows. Pest for which we're going to
cover in this course makes this
remarkably simple. Now let me give you a
couple of differences between manual
testing and automated testing. Manual
testing is timeintensive. It requires
dedicated human to test every feature
when it's developed. Plus, you're going
to retest all previous features. If you
make some changes in the code that might
be connected to the pre previous
feature, you get inconsistent results in
case of manual testing. Manual testing
is also errorprone. It's easy to miss
subs or forget to test specific scenario
under time pressure and you have to do
repetitive work. Same manual steps must
be performed again and again and again
for every code change. In case of
automated testing, it is lightning fast.
Execute hundreds of thousands of tests
in seconds with a single comment. You
are perfectly consistent. Tests run
identically every time, following exact
same steps without variation. You have
comprehensive coverage, meaning never
forget steps, never skip checks,
automated tests, always verify
everything and continuous validation to
run your tests on every comet, pull
request or deployment workflows inside
CI/CD pipelines that changes your
deployment way. Once you have written
those automated tests, they continue
working for you throughout the entire
project lifetime and reducing time and
effort for you in this project. Now,
what makes a test good test? When it is
clear and easily readable. When it is
focused on one thing, not multiple
things. When it is deterministic. Tests
must produce every time they execute
them the same result. No randomness, no
dependencies on external APIs or current
date, time or some other information.
Always produce the same result in the
same circumstances. Tests must be fast
and tests should be meaningful as well.
If you have a look just the test cases
what you have written for your
application, you should have good
understanding what your project does.
Laravel provides an exceptional out
ofthe-box solution for testing, making
it one of the most developer friendly
PHP frameworks for building robust test
suites. Everything you need is included
and thoughtfully designed to make
testing feel natural and intuitive. It
has well organized test structure inside
test folder. You have dedicated test
environment if you want. So you can
createin testing and just configure the
database or some other parameters only
for testing. You can have automated
database management as well and laral
provides really helpful testing
utilities for HTTP requests such as get
post to act as an authenticated user to
validate to interact with JSON APIs and
so on. Now I think it's time to open the
code editor and create our first test.
Now let's create new Larl project and
have a look at testing. First I'm going
to execute Laravel new Laravel testing
course. I'm going to choose React
Starter Kit because it really doesn't
matter but I'm going to make this a more
advanced type of application where we
have this single page application with
React and Laravel. So I'm going to
choose React. I'm going to stick with
Laravel's built-in authentication and
I'm going to choose Pest as the testing
framework. Let me give you a quick
explanation. What's the difference
between pest and phpunit? PHPUnit is old
and it is the core of the testing. Pest
is built on top of phpunit and it gives
more elegant and nicer syntax to work
with testing and pest 4 has additional
great features which makes it super
super nice to work with. So we're going
to choose past. I'm going to choose no
here and let's wait until the project is
created. I open the project using
phptorm. Then we're going to bring up
terminal and we're going to execute PHP
artisan test. This is the one way to
execute tests. And as you can see all 41
tests are passed or we can also execute
vendor bean pest. These tests are
located under test folder and let's have
a look quick look. So we have test PHP
which is the configuration file for
tests and we have then unit tests inside
the unit folder and at the moment we
don't have unit tests actually we have
only one example test and we have
feature tests. All the tests what have
been uh run right now are feature tests
at the moment. So we have this example
test and then we have the dashboard test
and some tests related to settings and
out as well. And each file also contains
several tests inside. All right. So this
is the structure. We have test folder.
We have test PHP and we have feature and
unit. And inside we're going to create
all the tests. I'm not going to go these
tests and just explore them. Instead,
I'm going to simply delete all files
from unit
as well as from feature because we're
going to create them manually. Now,
let's bring up terminal. And if we
execute vendor being test right now, we
see no tests found or I'm going to stick
with PHP artisan test. But before I
execute this, I'm going to type PHP
artisan test list, which is going to
give us actually column column list.
There are no comments defined in the
test name space. So there's nothing.
However, if we execute PHP artisan make
colon test dash help, then we are going
to see several flags associated to this.
So if we want to create new test file we
have to execute make test then provide
the test name and additionally we have
options to create a unit test to create
a past test which is the default one to
create PHP unit test and we're going to
talk about the differences between the
past and PHP unit test syntax right now.
So I'm going to show you both examples
and we have a couple of options. First
let's start with test and I'm going to
create new uh test. Let's call this test
or let's call this sample test.
Okay, sample test was created. We can
open this inside test folder feature by
default it created feature test and if
we click on this this is the feature
test. We are testing certain thing and
we are going to give it a proper
description. for example
should open homepage and it works.
So in this case we're accessing the
homepage of the application. We're
getting the response and we're checking
if that response has a status code 200.
It means our application is good and it
is working. All right. So we can bring a
terminal and execute PHP artisan test or
vendor being passed. Let me actually
execute PHP artisan test. Now we're
going to see that one simple test has
been passed. It should open homepage and
it works.
Now let's create unit test. PHP artisan
make test sample test but let's call
this d- let's give it flag d-unit.
We're going to hit the enter. Now we're
going to see that sample test is created
inside tests unit folder. Let's open and
have a look. Now this is the unit test.
The purpose of the unit test is to test
certain functionality. In most cases, it
should test some heavy logic, some
method or classes in your application.
It is not integration or feature test
meaning it is not making requests into
the application. So this is a very
simple test. It expects that certain
value to be true. All right. Now let's
bring terminal and execute PHP artisan
test again. Now we're going to see both
tests are passing. This is the unit
test. This is the feature test. Now this
in both cases we have saw we we have
seen the unit test as well as the
feature test but the syntax is pest. So
we are using pest. But as I mentioned,
pest is being built on on top of PHP
unit. So if you open composer JSON right
now, we're going to see that we have
pest here. We have past php and pest
plug-in level. Pest by the way is a
standalone package which is for testing
PHP applications but it's um is
developed by Nun Maduro which is the
core member of Laravel team and he also
developed this pest plugin Laravel and
pest works great with Laravel but the
thing is that if we open right now
composer lock file and if we search for
pestph php / test
we're going to find that here it is. So
this is the package installed and inside
its dependencies there's this PHP unit.
So internally it is still using PHP unit
just it is a wrapper with a nicer syntax
more intuitive in my opinion as well and
you write much less code as well. Now
let's bring up terminal and execute PHP
artisan make test. Let's call this make
test two uh two here and we're going to
provide a flag by the by default we know
that this is going to create past
feature test right now let's provide
flag called d-phpunit
now this is going to create this class
in a php unitit syntax if we open right
now this inside test folder feature
sample test 2 we're going to see that
This is a class. This is a typical
objectoriented way of testing your
applications. It is a class. It extends
this test case. Uh it u has this test
example. That example is the same text
you would give to your uh feature test
right here inside this test. So in our
case we should call this
it should underscore
open home age and it works. So this is
how we would name the method. Now let's
open terminal and execute PHP artisan
test and have a look.
Now all tests are running. Where is this
simple test two? Actually, it should end
with a test. That's why it wasn't
executed. We're going to call this
sample two test.
Sample two test. And we have to we have
to rename it. Apologies. Sample two
test. Okay, let's rename this. Now, it
was renamed as a file as well.
Now, let's execute PHP artisan test. And
we're going to see three tests passed.
And we see the these two tests are kind
of identical. So this is coming from
this sample two test and this is coming
from the sample test. This is the PHP
unit syntax. This is paste syntax. And
this is a typical uh unit test. Now
let's create new test in PHP unit syntax
which is going to be unit test. We're
going to execute PHP artisan make test.
Let's call this sample two test. Again
we want PHP unit syntax and we want unit
test not feature test anymore. Now it
created this file inside unit folder.
Again let's go inside unit folder. Open
this. And it looks very similar to what
we have already seen right here. Just
the assertion way how we check some
values is different. So in this case we
are using expectations API. we expect
something to be something but in this
case we are using in PHP unit we are
using assertions API we assert this
value to be true so uh PHP unit this one
only supports assertions API it doesn't
support expectations API but
past actually supports both so we can
take this code this assert true equals
true and we can put this here we can
comment this part and we can execute
tests again and we're going to see that
it works as well. So two unit tests are
executed, two feature tests are
executed. So that's why I personally
like writing test syntax because this is
the same as this and we don't need to
create the class and everything and also
naming is like very very inconvenient in
your PHPUnit way. So in this case we can
just write
it checks that the value is
is true. Right? So we save this execute
this but in this case we have to give it
a name with underscores. Let's talk a
little bit about configuration of our
tests or how we can change the database
or some environment variables
specifically for tests. All tests by
default are using PHP unit.xml XML file
if the test needs the database. So here
we have these environment variables the
ENV is testing we have this maintenance
driver to be file and all the other
things and the DB connection is SQLite
and the database is in a memory
database. So whenever we execute the
test it is going to do all the things
inside the memory create migrations and
everything and then uh the memory is
gone basically once the test is
finished. If we comment out this part
and execute our tests and our tests are
using database those tests will be
executed on our primary database what we
have written inside file.
If we don't want that we also have
ability to create env.esting
file exclusively for the environment
variables that is specific for testing.
So in this case we can delete everything
from here let's say and we can only
leave DB connection related information.
We can specify the database connection
to be SQLite uh and database
DB database to be in memory as well. So
we also have that flexibility or if you
want to override a specific environment
variable from the default in you can do
this from here as well. Let's say you
want to change the mailer to be log
instead of some SMTP. So all that is
possible through this env.esting
file from this phpunit.xml
file. We also have ability to customize
the directories for unit tests as well
as for feature test. As you can see by
default we have the unit tests are
created inside test unit and feature
tests are inside tests feature. So you
can customize this if you want.
Now let's close these two files and
let's go inside. Actually um I'm going
to close this. I'm going to delete this
yen testing as well because I prefer it
to be using uh the settings what I have
defined right here and I'm going to
uncomment this part as well. Okay. Now
let's go inside test folder and open
pest.php.
So this is the main configuration file
for pest. From here we can decide in
which browser by default the test should
be run, what configuration options it
should have by default and so on. So if
you have a look right here, all tests
are extending tests test case and also
it uses refresh database class every
time which means that every time we
execute the test it is going to just uh
refresh the entire database drop create
migrations and then it is going to run
them inside features. Okay, all tests
are basically all feature tests will be
inside feature target so-called this as
an example here we have extended
expectations and added a new
expectations method to be one. So using
this way we have ability to define
custom expectations here as an example
let's write I'm going to actually paste
this I think it's going to be easier and
I will explain this. So to be uid as an
example so we are defining new
expectations method where this value
needs to be needs to match the UID and
that's it. So whatever is returned from
here true or false it's going to be
tested and we can use this to be UUID
inside our tests. Additionally I'm going
to do this right here. We have ability
to customize certain things globally
such such as I'm going to call test
browser
and let's say that we want to run this
um always in Firefox. So we can do this
in the following way. Of course, we have
other browsers such as in Safari, in
Chrome and so on. We also have ability
to customize the global timeout. By
default, test is waiting for several
seconds, I think 5 seconds until the
element gets visible in the browser to
decide if the test is failed or not. And
we have flexibility for this. We can
provide different number here. Let's say
10,000 milliseconds, 10 seconds. And
based on that now pest is going to wait
a little bit more. We also have ability
to provide a global user agent to all
tests what we execute right here. And I
think that might be helpful in certain
cases. Now I'm going to delete all
feature tests and sample to unit test as
well. And I'm going to leave only this
sample test and we're going to
understand how we're going to work with
expectations API and what expectations
are available. As I mentioned, test
replaces the assertions API with
expectations. Uh it doesn't actually
replace assertions is still there. it
still works but expectations API is more
um more flexible and more readable in my
opinion as well. I'm going to remove
this assert true okay expect something
that something can be an object can be
number can be string it can be anything
so let's specify expect five as an
example and then it gives us the object
which has a lot of a lot of functions
like expect five to be something as an
example to be five so we expect some
number and that number might be received
from database from some APIs let's say
that is a number which um our math class
gave us and we expect that number to be
3.14. Let's say this is a pi, right? So
pi equals 3.14 and then we expect that
pi which is by the way received from
some math class to be 3.14. So once we
execute this, we're going to see that
test is passing. If for some reason that
function or class or whatever is
returning result from a function
or from a class. Okay. So if for some
reason that class doesn't work properly
and returns something else then our test
will fail. Right? So this to be um
something is just one example but we
have a lot of a lot of expectations and
I'm not going to go through all of them
but we have some of the most common ones
such as to be string as an example. So
we expect as an example here zura to be
string we can specify that zura must be
string it must contain and we can chain
also to contain uh z inside there. So
let's remove this pi from here.
Now if we execute the following test,
we're going to see that it is passing
and we have two assertions as well. One
assertion is that it needs to be string
and second assertion is that it should
contain Z inside. I'm going to open test
expectations
um documentation page and here we're
going to see everything that is
possible. All right. So if we scroll
down below, we have this 2B to be array
to be between and I'm not going to read
all of them obviously, but I encourage
you to have a look and understand what
expectations are available because that
expectations define how easily you can
write tests. As an example, you might
get an array and you expect that array
to have exactly three elements inside
there to have count three. And we also
can negate our um our expectation. As an
example, we can expect Zura to be string
to contain Z and then we can chain not
to contain lowerase Z as an example. We
expect it to be uppercase C. So if we
execute this now, we're going to see
test is passing. And now we have three
assertions. We can also chain uh to
because we already use not here. We
don't need not anymore. So we negated
the entire uh operator entire
expectations and then to uh to be
integer as an example. So we expect that
not to contain Z and not to be integer.
If we execute this oh actually um it's
my bad. So we need this not to contain
and not to be integer. Now we see four
assertions are passing. We need to use
this not before every um every
assertion. Now let's see another
example. I'm going to use expect and
provide array here. Let's say we have
three elements inside array one,
two and three. And then we expect to be
array
that's one assertion. Second is that it
to have count of three. And I also want
to uh check that it should contain
element two. Now uh why do I see this
multiple expectations can be changed to
changed together.
That's page for sure thing.
Okay. Expect and oh okay. So that's
that's an syntactic sugar on top of what
I just shown to you. It uh combine these
things. Actually I prefer to have them
splitted because this is one thing
testing strings. This is one thing
testing array. All right. So let's bring
up terminal. Execute this. Now we see
seven assertions. The test is passing.
Now let me show you another example. I'm
going to use expect and provide one two
and three. Then we can again use some
expectation method such as to have count
three. But that's not the main thing. So
then I'm going to call sequence on that.
And when I call sequence, we have
ability to provide callback functions
for each value of this array. So I'm
going to use fn the arrow function
basically right here. And then the value
inside is already an instance of
expectations API. So in this case on the
value we can directly call to be integer
as an example. So if we save this and we
execute that we're going to see that the
test is passing and we have 11
assertions right now. Why do we have 11?
Let's count this 1 2 3 4 5 6 7 8 and
then this function is executed three
times for each element of the array.
That's the thing. If I remove one
element from this array and re-execute
that
uh what's wrong? Oh, this this is what's
wrong. We expected to have three
elements inside but and now we have two
elements inside. Actually, let's let's
remove this part. Not necessary. Okay,
now we have nine assertions. Now, let's
return this back into three. And we are
going to have now 10 assertions.
We had 11 previously because we had this
to have count as well. But the thing is
that we have ability to provide multiple
callback functions here. Um and those
callback functions will be iterated. So
in this case if we provide three
callback functions we can define to be
one. The first callback function the
value inside the first callback function
to be one. uh the value inside the
second callback function to be two and
the value inside the third callback
function to be three. So if we execute
this all tests are passing. That's
because those callback functions are
executed sequentially on these elements.
All right, I hope that makes sense.
Now let's create a user instance which
is going to be new user and we can
provide um for example first underscore
name to visura okay I'm going to leave
only first name we are not actually
creating the user in the database but I
want to show you using expect providing
user and then we can use the method to
be instance of in the user class. So in
the following way we can replace this
qualifier using import. In the following
test we can test in the following
expectations. We can test if the user is
an instance of a specific class. If we
execute this we're going to see that the
test is passing. We also have a method
expect something as an example. Uh let's
replace this into an array and provide
user here to contain only instances of
if we have a list of users providing
here then we can provide user class here
using the following assertions we can
test using the following expectations we
can test if the following array only
contains instances of the given class.
That is al also helpful one. So again, I
encourage you to have a look at the
expectations API and just read uh them.
So this is my homework to you. Just read
what expectations are available. Uh and
whenever you need to write a real test,
unit test or um some assertion, some
feature test, then you know which one
you can use. Now I'm going to create one
feature test as well and let's see a
couple of um expectations on the feature
side as well.
um PHP artisan make test sample test but
this case um it's going to be a feature
test. Okay. So inside feature let's open
this. So we are using assertion here to
check if the response has the status of
200. We can chain different assertions
here as an example assert cextable. So
we expect that when we open the
application the status code should be
200 and there should be text allarl. Now
let's execute tests. PHP artisan test.
And look at this. Um where's this
feature test? Okay, it has the example
test. We should give it proper
descriptive name. Uh but the thing is
that it is passing two assertions. Uh 14
assertions more specifically. So that's
a total number of assertions actually.
But we have two assertions here. And we
have a bunch of different assertions
here which I'm not going to um focus
right now. But step by step we are going
to get familiar all of them. Not all of
them but the most important ones. Now
let's write actual unit tests for my
service class. But now I'm going to
delete again this feature and unit
tests. And let's create a new unit test.
I'm going to call this PHP artisan make
test and let's call this price
calculator test. Let's hit the enter.
And now let me show this. Actually,
sorry. I should have created inside a
unit folder because we are um writing
unit test right now. I'm going to delete
this feature test. Now, if we go inside
app services and open this price
calculator class, it has several
methods. So, this one is apply
percentage discount. We pass the price,
we pass the percent and then we check if
the percent and the price doesn't fit in
the uh valid amounts numbers. Then we
throw this invalid discount percent um
exception otherwise we simply apply the
given percent as a discount on the given
price. Okay, we have second method apply
fixed discount. We give the price and
fixed amount and then we apply this
amount as a discount on the given price.
We also have added tax. We give the
price. We give the tax percent and we
add the tax percent on the following
price. And we have this final price
where we give the price. We have this uh
discount percent and tax percent as
well. And we calculate um first we apply
this discount and then we add tax. All
right. So we have these four methods and
I want to write now tests for all four
methods unit tests and remember unit
tests should not use HTTP should not use
any external services should not use a
database we are testing simply pure
logic of a class or a method okay so
this is the first test and let's give it
applies percentage discount now we're
going to create an instance of this
price calculator. So price calculator
equals new apps service price
calculator. Okay, perfect. Then we're
going to use expect price calculator
actually let's call this calculator to
make this simpler. Then on that
calculator we're going to call apply
percentage discount. We provide the
price as an example 100 and let's
provide the discount of 20%. So once
that discount is applied on price then
we expect it to be at.
All right let's bring up terminal and
execute PHP artisan test
and look at this applies percentage
discount. The test is failing uh because
that 80.0
is identical to 80. So what we need to
do it returns a float number. So we have
to provide 80.0 here as well. Now if we
execute this now we see that test is
passing. So that's one example. Actually
we expected integer to be returned but
inside the method float was returned.
So that's one test. Now let's write a
second test. And by the way we have two
ways to write the test. one is using
this test method. And if we execute
this, we see here applies percentage
discount. But if we change this into it,
that's a syntactic sugar on top of that.
So then if we execute this now, we see
it applies percentage discount. So
that's just syntactic sugar on top of
the test method. So if you open this it,
we see that it is actually calling test
internally. So let's use it here and I'm
going to write second test it uh rounds
discounted rice provide a function here
then again we have to create calculator
equals new app services price calculator
and let's actually replace this uh into
import. So we have this import at the
top then create instances. Okay, now we
have the calculator. Now we expect on
calculator apply percent discount. Let's
give the price to be 100 and the percent
to be something like 12
345.
All right. And then the result we expect
it to be
um 87.66.
So why we expect this actually? So this
12.345%
uh discounted on 100 means that we have
remaining
uh 80
um 87
uh 655
right but because we apply rounding this
is what we are testing. If we go in the
price calculator, we see that on that
method, we have everything rounded with
precision two. So instead of 87.655,
we expect it to be 87.66
just to test that the rounding works
successfully. All right. Now if you
bring up terminal and execute PHP test,
now we see two tests are uh passing. It
rounds discounted price. Now let's see
how we can groups similar tests into the
same group. We're going to take these
two tests which is related to applying
percentage discount. So I'm going to
take this and then I'm going to use the
method called describe and I'm going to
give it uh like price calculator dash
percentage discount. Okay. So then we
provide a callback function here and we
are writing all our tests inside that
callback function. I'm going to paste
them. We're going to save and execute
them. And now we see that this is the
group now. Price calculator percentage
discount it applies percentage discount
and in the same group we have it rounds
discounted price. So we have two tests
and now we can go ahead and write um
another test which is for the fixed
discount. I'm going to introduce another
describe here price calculator fixed
discount and let's create um several
tests first. Let me define those tests.
This is also a good practice to define
your tests first. So applies fixed
discount. So this is the one test we're
going to write. Second is going to be
eat um it
uh never returns
negative price.
Um
I don't want it to be skipped. The
autocomp completion is sometimes really
good but sometimes it is annoying when I
don't want when I want step by step to
introduce something.
Another test it throws
or negative fixed amount we need
function here and I think the three
tests are kind of enough. So we know
that inside this group we're going to
have those three uh tests. Hey, can you
do me a favor and pause this right now?
Hit the like button, subscribe if you
are not yet because I'm working on some
really interesting videos for you and
then continue the course. Thank you.
Now, if we bring up terminal and execute
this, we're going to see that these
tests don't perform any assertions. So,
we see this in yellow. This kind of good
indicator as well that we don't have
those tests implemented, right? So,
let's start with the first one. It
applies fixed discount. We're going to
create calculator here.
Then on that calculator, we're going to
call apply fixed discount with
120. But we're going to wrap this inside
expect.
And then on that expect we're going to
call the result to be 80.0.
So we save this. We execute this. And we
see that this test has been passed. We
have other two tests where we don't have
anything implemented. Right now the next
one is to test that it never returns a
negative price. Right? So we're going to
provide we're going to create the
calculator then on expect we are going
to call calculator apply fixed discount
and in this case I'm going to provide
the value to be 50 and the percent to be
100 right so and then we expect the
result
to be 0.00 0 0. So we don't expect the
result to become negative even though we
applied uh $100 discount dollar or 100
amount discount on the 50 uh price.
Right? So let's bring up terminal
execute that and the test is actually
failing. Oh sorry not expect to be 2B
0.00 0. Okay, let's execute this. And
now we see that this test is also
passing. And the last one we're going to
test that it throws for negative fixed
amount. So we create the calculator.
Then on that calculator
uh we are calling
expect
calculator apply fixed discount and we
provide minus 10 to be the discount
price and we expect
expect exception. So there are a couple
of ways. So we can um we can use the
method to throw. So we expect that it to
throw invalid argument exception. That's
the one way to do this. We're going to
execute this and we see the following
error. Well, this is the thing. Yeah,
that's a good point. So we the test
wasn't passed. Uh instead we saw an
exception. That's because this method
throw an exception and that happened in
the main thread where the test was
running. So in order to test this, we
need this error. We need to wrap this
inside the function. Now when we put
this inside the function, test will
check if it throws an exception and
catch it and properly gives us the
result. Now we see that uh it throws for
negative fixed amount. The test is
passing. So it throws the exception. So
if it throws the exception, you should
not use in the way like this. Instead,
we're going to put this inside a
function. Now, let's create another
group. And I'm going to give this a
challenge to you. So, we're going to
test um let's create the group first.
And we're going to test that it adds tax
and final price that it tests the final
price as well. Okay. First, we need
it
adds tax to the price.
Good. Second is that it rounds tax
price.
Rounds
taxed price.
And the third one is it calculates final
price with discount and tax. We can
provide additional text as well with
discount and tax. Now this is a
challenge to you. Please go ahead and
implement these three unit tests based
on this price calculator class. You can
get the price calculator source code
from the project um and you can find the
project link in the video description
and implement these three methods and
then come back and see my solution.
Okay, I hope you made it. Now let's see
how I'm going to do this. First
obviously we're going to create the U
calculator. Next on that calculator,
we're going to call method add tax and
we're going to wrap this inside expect.
We add 10% tax on top of that or we can
replace this into like 21% as well to
have that not that round number. And
then we expect the price to be 121. This
is the first test. In the next test,
again we're going to create the
calculator. then we expect um to add tax
on that calculator. So we take this
price to be 100 uh point one two three
whatever number here right then we add
tax percent to be 21%. In order to
decide what value we're going to expect
we have to do a little bit of
calculation on ourselves as well right
so let's bring up terminal and in the
calculator we're going to take this 100
100.12345.
We're going to multiply this on 21
which is a tax percent and we're going
to divide this on 100 and this is going
to be the tax amount value. Right? Then
when we add this 21.025
025
whatever is this we're going to plus
100.12
3 4 and five. So finally we get
12149.
So if we round this 12149
into two uh two um precision then we
should expect here to be 15
right so let's execute the test it
rounds tax price this is correct and the
last one is remaining the autocomp
completion gave us wrong value here so
that's why it's kind of annoying rank
sometimes but we calculate it ourselves
and we know what we are expecting that
the tax price should be rounded properly
and the final one is to calculate the
final price with discount and tax. Again
we're going to create the calculator.
Let me do this step by step. uh then let
me call expect on the calculator
to call final price and let's provide
100 as a final price not the final as an
initial price. Then we provide 10 here
inside the discount percent and we
provide 21 as a tax percent. So we
expect the value to be something. So we
have to calculate this by ourselves and
then we decide what we expect. So if we
open again terminal
a little bit of calculator. So then we
take that 100 and we're going to
subtract the discounted price which is
going to be 10% and so finally we have
90 remaining. Right? And then we're
going to add 21%
on 90. Right? So 90 plus 90 ultiplied on
21 divided on 100. So that is uh 108.9.
So finally we expect
it to be
108.9.
Now we save this
and execute PHP artisan test. And let's
have a look.
Oh, we have semicolon missing here. Not
a big deal. Okay, one test was failed.
Expect doesn't exist in double again.
That should be 2B. Not expect but 2B.
Okay, now all tests are passing. And
congratulations. Right now we have
written a real world scenario of unit
tests of our service class. And we have
written how many tests? We have eight
tests written in our project which is
pretty cool and um not as complex as you
might find in uh real projects uh large
projects but this is a good start and
good um good unit tests. This is brand
new Laravel React starter kit which we
have installed and it has the welcome
page, it has a login page and a register
page and we also have uh on the login
page forget password page as well. So
now we are going to write feature tests
and write test to make sure that the
homepage welcome page opens uh the login
register pages are opening and like we
see the information on those pages right
so let's open terminal
and I'm going to execute PHP artisan
make test and I'm going to call this
public pages test however we can put
this inside a subfolder by providing
something the subfolder name. So we can
call this um roads dot or not dot but
slashpublic pages test. We hit the
enter. Now we see this public pages test
is created inside roles folder. So we
can open this public pages test and we
see an example uh test right here. So
let me replace this with it and that
should be shows welcome page. By the
way, this is one way how we're going to
make request this get. But the second
way is to simply use get method in which
case we have to import the get function
as well from past lar. Okay. So we you
can choose whichever you want. I'm going
to stick with this get here.
Okay. So this shows the welcome page and
makes the status is correct. Let's bring
up the terminal and I'm going to execute
PHP artisan test and we're going to see
that this feature test is actually
passing.
Now let's create another test. It
uh shows the login page. All right. Now
in this case we're going to make request
response equals get login on the login
endpoint. We can save this into a
response variable and then on that
response variable we can call some
assertions or another way is to change
several methods. So we get the response
then we can check the status uh needs to
be 200. What else we expect on the
homepage? Let's open the browser. Click
on login and then we expect the login to
your account text to be also visible. Um
and that's it. Yeah. So then we can use
another assertion like assert C login to
your account. Optionally we have a lot
of assertions here. Assert and assert
accepted. Assert accepted um basically
checks a specific status code. It's a
202 status code. So we are not we don't
need to cover all of them. But let's
have a look. Assert those are related to
status codes. Assert okay is again the
200 status code
unauthorized assert cookie if the cookie
contains specific value. Assert C and
assert don't see. So we used assert C
and we expect the following text to be
displayed. Um and we can use assert
don't see as well such as um such as
welcome to Laravel or dashboard. So we
don't expect dashboard to be displayed
when we access to the login page. Let's
bring up the terminal and execute PHP
artisan test and let's have a look.
Login to your account. This is not
visible. So this is the thing. So this
is not browser testing. It is making it
is making curl request getting the HTML
and then inside that HTML it is
searching for the following um for the
following content. And I expect this is
not going to work as well. And I'm going
to explain why.
Okay. Yeah, this is not going to work.
And the reason is that we have inertia
laral inertia type of application. All
right. And the content is rendered
dynamically. So if we view the page
source, this is what um paste right now
sees when we make the following request.
This is it. This is inertia page and we
don't see login here. Log in do we see?
No, we don't see. That's the reason. And
we are using the login.tsx file which is
dynamically rendered in the browser.
That's why uh pest for's browser testing
is absolutely amazing and using browser
testing we will be able to test all this
functionality like the login and the
text is visible and the buttons are
working and the redirects are working
and everything. To get started with
browser testing, we have to install pest
plug-in browser as well as playright
because internally pest plug-in um 4
version 4 for browser testing is using
playright. So we're going to copy the
following command bring up terminal
paste this and hit the enter. So at this
stage, look at this. It is downloading
Chromium uh browser and it is going to
actually test everything inside the real
browser which is uh open-source version
of Chrome. In my case, some of the
playright dependencies we're missing. So
I can execute the following command or I
can execute the following commands.
Okay, the dependencies have been
installed and if we go right now inside
this public pages test, we can replace
get
with visit. So we use visit to have a
look um and to open this in the browser.
Assert status function doesn't exist
after the visit method. So we have to
remove this. So we expect login to be
displayed and we expect dashboard text
not to be displayed as an example. We
can replace this with visit as well. So
the thing is that this one was a failing
because of the client side rendering.
Now let's bring up terminal and execute
PHP artisan test. The browser testing
needs a little bit more time. Uh yep
this is not good. So we don't have
assert status anymore. Instead we can
replace this assert C. We expect to see
Laravel. If we see Laravel we know this
is good. and 200 status code was
returned. Let's re-execute PHP artisan
test.
Now we see that the tests are actually
passed. It shows welcome page. It shows
the login page as well. So what we can
do is call debug right here. Then
execute this. And now it opened the
browser and it stopped at point of
debug. So it checked and saw that the
login was existed there and it then it
called the debug. So it paused open the
browser so that we can debug what is
happening right here in the following
screen. I can zoom in this part. So this
is it. Okay, we're going to close that.
Press any kick to continue. If you want
to see everything in a headed mode. So
we have ability to provide um d- headed
flag.
So the browser opens, test is completed,
it's closed, then it's open. So for
every test, browser is opened and we see
everything in the browser. However, it
is fast and we don't actually notice
what's going on. But that is browser
testing. Of course, it is slightly
slower than not non browser testing. the
typical testing which is available in
pest version 3. But the browser testing
gives you ability to test uh everything
actually in the browser like we are not
able to test if the login page even
opens in Laravel React version um of the
starter kit but we are able to do this
thing using browserbased testing. Now
let's write a test to see if login
actually works. So we're going to use uh
it tests
or um yeah it tests that login works.
Okay. So we need to first create the
user and then use that user for
authentication. user equals uh new not
new but the user factory create and we
can provide password here.
Password needs to be brypt of something
like password
one to three. Uh the user model needs to
be imported from app models user. That's
perfect. And now we can use that user's
email and the password is one to three
to actually type them in the browser. So
we're going to use visit right now
login. Actually we can do we can go even
step further and we can do like this
visit on slash
then we're going to uh click
we're going to click
on the button which is called login. So
if we access slash then we have the
following button. So we're clicking on
this and this is something this can be
another test. So we uh we tested that
when we access login URL we see this uh
login text right there and we don't see
dashboard. But another test inside here
can be that when we click on slash and
when we visit on slash then click on
login then we see the login text and we
can also check
assert path is /lo. So the URL was
changed into login. So that's also a
good test which shows the login page
that's for showing it right. So in our
case we can directly visit to login. We
know that now login page is opening. So
this proves that the login page opens
from homepage. Now when we visit login
we can type email to be the user's email
address. Whatever it is
email we can type the password to be
password one two three. And then we
press a login button. Let's make sure
that the button has the same text login
right here. And then we check that the
path needs to be dashboard right. So we
save this. Let's bring up terminal and
execute PHP artisan test. Let's remove
this headed. It needs two three seconds.
This error basically means that for just
created user two factor authentication
is enabled. And when we entered email
and password in the form and hit the
press button, it redirected us into the
two factor page. So if we put a debug
here and re-execute the same test, it
will open debugger. Look at this. So and
this is it. So now it expects us to
enter two factor authentication code and
the test was failed after some time.
Okay. So in order to fix that we have to
create the user without two factor
authentication. If you open user factory
uh right here inside the definition we
have all the fields defined and we have
two factor secret and two factory uh
recovery codes and confirmed at. So that
means that whenever we create the user
those two factor fields are set. So we
can actually override those fields for
that specific user creation. we provide
the password but we can also provide
these fields. So this is going to be
like this. So we provide null everywhere
and now if we re-execute this test now
the dashboard is visible and the test
was passed. So and just like this if we
want to test the two factor thing that's
a different story but right now we
tested that the if the two factor is not
authentic is not activated on the user
the user is created and the user is able
to visit the login page enter those
information click on login and the user
is redirected into dashboard I'm going
to remove this debug now besides this
type and press let's have a look what
other methods are available to interact
with elements so we have click we have
text. Uh this text basically method gets
the text of the element which matches
the given selector. So it simply returns
the text. So finally we're going to get
the text right here. This one returns
the attribute. Let's have a look. So we
have check to check the checkbox uh with
a specific value.
We have ability to check radio as well.
We have ability to attach a file to the
input type file. The press basically is
the same as click. We also have
possibility to select something in a
selector. So in the country selector, we
can provide us if it supports multiple
values. We can do this in the following
way. We have we have ability to submit
the page. If the page has form inside
there, we have ability to get the value.
So we have a lot of things basically
what we need to interact with the uh
element. So we also have ability to get
the current URL of the page to wait for
some time. Um and u this is pretty cool
also regarding this debugging test we
have already seen debug method. Let's
have a look at this screenshot method
which has two properties to take the
full page screenshot and to provide the
file name as well. So I'm going to open
now and let's provide screenshot here
and I'm going to just leave this with
default values. So we can execute this.
It's going to take the screenshot and
save it.
Okay, the screenshots are typically
inside tests browser screenshot and
let's click on this and this is it. So
it gave us the screenshot that the login
works. Optionally we can provide the
file name custom file name or the full
page screenshot can be taken as well or
we also have this screenshot element u
method as well and it's going to take
the screenshot of a specific element not
the entire page we also have ability to
open tinker at specific point of the
code if the code is failing at some time
because something is not correct in the
database we can open the tinker at that
point and observe the database and see
what's there and uh we covered that we
can open this in a headed mode as well
if we want. So
now let's have a look at some of the
assertions that is available inside
browsers API browser testing API. So we
have ability to test if the page title
is specific thing or title contains
something. We can check if the checkbox
is checked or not. We can see if the
radio is selected or not. We can test if
the element has a specific attribute or
if the attribute is missing. We can see
if the button is enabled um or not. We
can see if the URL is a specific thing
or not. So we have a lot of things
basically what you think. We can check
how many items basically of a specific
selector we have in the DOM. Just have a
look and understand what is available
here. So we can also check if the
screenshot matches previously taken
screenshots which which is insane. So
that's that one is uh like a really
good. Now let's test if our application
opens this cyber menu on mobile version.
So we're going to open
and it
uh tests that mobile menu works.
Okay, great. First, what we're going to
do is create the user. So, I'm going to
copy the following part and then we're
going to login into the dashboard as
well. So, I'm going to copy the
following part completely. Paste here.
So, once we are inside the dashboard, we
can remove this screenshot part. I don't
want the screenshot to be taken. So,
once we are inside the dashboard already
and we checked, then we're going to
click on the following element. That
element has data slot sidebar sidebar
trigger. So we're going to select an
element based on this.
Let's search for press
data slot equals sidebar toggle.
Cool. So we press on this and then we
expect something to be visible such as
we expect platform to be visible or we
can even trust the following text
Laravel starter kit. This is a text I
think Laravel starter kit to be visible
visible assert visible Laravel starter
kit and finally let's call debug to see
what happens in the browser.
Now we have been authenticated actually
uh my bad. We should execute this on
mobile. That's the thing. So we're going
to call on then we're going to call
mobile. Now we save and execute that.
Now this was executed on mobile but it
does not press on it. So I guess the
test is going to fail.
Okay, it was failed because
that data slot sidebar toggle press did
not work because this is called sidebar
trigger not sidebar toggle. Let's
reexecute that. Now look at this. So
this opened and laral starter key text
is visible. So probably the test is
going to pass. Here we go. Perfect. Now
let's write another test in which case
we will be accessing several pages on
the website. I'm going to log out from
here. We're going to access this
homepage login and register pages as
well. And we're going to check that
there are no errors in the console and
there are no JavaScript errors and no
console locks basically. So let's write
the test. It
tests that there
are no console logs and errors.
Great. So let's write this function. Now
we're going to access to several pages
in the following way. We use visit but
we're providing array right here. So we
access slash
then we access uh plugin then we access
register. Okay three pages are enough.
So then we can destructure that and take
it as home
login and register.
All right. So then individually we can
write some assertions on home login and
register. On home we can see assert
title to be Laravel starter kit or
whatever is the title uh for the
homepage. It is welcome dash laravel.
Welcome dash space d- laravel. This
should be the title for the home. Let's
duplicate these two times. For login
there should be log in Laravel
log in Laravel and for registration it
should be register Laravel register
Laravel also on home as well as on
basically uh let me do the following.
I'm going to assign this into pages.
Then I'm going to dstructure them from
pages. And then on all pages I can do
the following. Where's the register?
Register is missing here. Okay. On all
pages I can call assert no console logs
plus assert
no JavaScript errors. Let's bring up
terminal and execute PHP artisan test.
Actually I want to debug this as well.
So I love debugging and seeing what's
going on. So we can execute PHP and
test. Let's see it is visiting multiple
pages. So that's one one test by the
way. And we have to remove debug here.
We don't want debug here. Okay. So we
can execute only that specific test if
we provide only here. So we we need only
the this test right now. PHP artisan uh
test. Okay, let's see what's the what's
the issue. Oh,
semicolon is missing. Okay, let's
execute that. Now, it opened multiple
windows at the same time and it is
checking the console logs internally and
it expects us to press any key and
something was failed. Actually it was
failed in debug mode but it it is
passing in the um headless mode. If we
have a look at the browser testing
documentation we also have ability to
configure custom user agent when we are
accessing the page. We also have
possibility to spec to provide a
specific a geo location to configure
local or to configure the time zone. So
all that is possible and again I
encourage you to have a look at the
browser testing documentation because
you're going to learn a lot of things
there. Right now we are only running the
following test which I'm going to
uncomment and I'm going to execute PHP
test but we also have ability to provide
d- parallel. This is going to execute
our tests in parallel mode and this is
going to significantly speed up the
process of testing. If you have hundreds
of tests in your application, we have
several tests in this file and the file
is called public pages test. It's not
logical. So I'm going to move some of
the tests from here into a separate
file.
We are going to take this test that
login works. Test that mobile menu works
as well. And the rest can be left here.
So this is for guest users. This as well
and this as well. So I'm going to bring
up terminal and execute PHP artisan make
test. We can call this out pages
test.
We hit the enter out pages test. We open
that. I'm going to replace this with my
tests
and we need to make sure that user is
imported dash parallel. This is going to
execute our tests in parallel mode and
this is going to significantly speed up
the process of testing if you have
hundreds of tests in your application.
We have several tests in this file and
the file is called public pages test.
It's not logical. So I'm going to move
some of the tests from here into a
separate file.
We are going to take this test that
login works. test that mobile menu works
as well and the rest can be left here.
So this is for guest users this as well
and this as well. So I'm going to bring
up terminal and execute PHP artisan make
test. We can call this out pages
test.
We hit the enter out pages test. We open
that. I'm going to replace this with my
tests.
And we need to make sure that user is
imported. If we have specific cases when
we want to test databases, this is the
lesson for you. So if you want to always
refresh the database, drop migrations,
reapply them, then you're going to use
this refresh database class in your test
configuration file, which by default is
there. If you want your test to run on a
MySQL database if you have something
something specific which SQLite for
which SQLite is not enough then you can
create.esting
file and configure the MySQL database
from here. Okay.
So if you want to create new records
we're going to use user factory create
inside our test. If you want to create
multiple uh records then we can do in
the following way. If we want to execute
the entire seed file, then we can call
this seed in our test. That's just for
the information.
Now let's see how we can test the model
attributes. Let's say we have the user
which has first and last and we provide
first and last and then the user has
composed column which is the combination
of first and last and we write the
following thing. expect you full name to
be surah in my case. So in the following
way we can test if the attributes match
to the specific thing. Another example
is to test the relationships. Let's say
we have the user model which has a
relation to posts model and we have post
model and migration defined in our
database. So using factories we create
user which has posts three. So this is
going to create the user with three
posts
and if everything is set up properly and
this is exactly what we are testing here
then user posts need to have three
count. So there will be exactly three
posts for this user and because we are
refreshing the database every time when
we execute the test there will not be
any additional posts in the database and
the user and its posts are created right
here. Okay, the next thing is to test if
the post belongs to a specific user. In
this case, we do it in a reverse order.
So we create post but we create this for
a user. So the user will also be
created. And to test the relation if it
works or not. Then from the post we
access user property and check that the
user not to be null.
Okay. If we want to test the query
scopes, let's say we have defined a
published scope on the uh post model,
then we create two posts. First, we
create with a published property true.
Second, we create with published
property false. And when we select all
posts with published scope, we expect
the count to be one. And in the
following way we will be able to test
any scopes what you have defined for
your project. You create records for the
to satisfy the scope criteria and to not
satisfy the scope criteria as well.
Okay. Next if we want to test the CRUD
for a specific model. So we create the
product actually not create but instead
we take the array of this product. So
user factory create actually creates
that in the database. But user factory
make simply returns the model. It
doesn't save it into the database. So
then we call this product to array to
convert this into an associated array
and we call product create to create
that in the database. Why we do this? We
don't use a directly factory create
because we want to test if the uh
fillable properties are set correctly on
the product model. So instead of product
factory create we use product create
which is the way we would typically use
in our uh controller where we get this
validated data and we pass this inside
create method. So in this case if some
of the fillable properties are not set
up on the product model or something is
wrong that product create will not be
will not be working and thus the expect
product count to be one will fail as
well. So in the following way we take
this product which we just created we
perform update for the price and we then
refresh that product. So we reselect
that product from the database and we
expect that the new product's price to
be 200. And finally we delete the
product and we expect the product count
in the database to be zero. Okay. Next
let's uh test soft deletes as well. If
we have a post which has deleted at
column in the database and we want to
test if it's soft deleted properly. We
create a post. We call soft we call
delete on the post and then we use the
following assertion assert soft deleted
and we provide the table name and we
provided the ID of the post we just
deleted and then assert soft deleted
will check that the post exists in the
database and it has this deleted at
column set after which we call product
restore which means the deleted at
becomes null again in the database and
Then when we reselect this post with a
fresh method, we ch we check that
deleted at to be null. Okay, perfect.
Next is to um next and I this is the
final thing to check several assertions
in the database. So we can use assert
database has or assert database missing
and provide the table name and the
parameters based on which we're going to
check if there is any user in the
database which has email test
atample.com and the second test will
check if that there must be orders uh
with canceled status missing in the
database. So this one will pass if user
exists with email testample.com. This
one will pass if there are no orders
with status canceled in the database.
Finally, I'm going to give you a couple
of recommendations. If you are working
with a database and testing some models
and relations, I recommend to use
refresh database to always start with a
clean data and use factories everywhere.
um and test the behavior such as scopes,
relationships and not eloquent internals
and write small and atomic tests as
well. Now let's talk about hooks local
hooks as well as global hooks which can
be executed once per file or once
globally or once per test. If we have a
look here, we have two tests and in both
cases we are creating a user without
two-actor authentication. First of all,
if we open this two-factor, we have this
without two factor method. So I can take
this I can remove these three lines and
I can use the method here. Next I am
going to define before
before each. before each is executed
before every test in this particular
file. So in this case I'm going to
create this user equals I'm going to cut
this out and paste here. So I'm creating
a user assigning into this user and now
I can use this user here. Scroll down
below. I can remove this part completely
and I can use this user here as well.
Now let's bring terminal and execute PHP
artisan test
and we're going to see
the out pages test are passing. Perfect.
We also have before all. Before all is
executed once in this file and before
each is executed for each test file. We
can also add after each and after all in
the exact same way. However, we also
have global hooks. If we open this past
configuration file, we can use past
function here. And then we can add
before each.
We can also add before all,
after each,
and after all.
And the thing is that before each is
executed for every test inside our
project. before all is executed for
every test file in our project. After
each and after all works in the same way
and if we have a global hook defined
here and local hook defined here first
global before each is executed this one
then local before each in the same way
before all works but first
after each local after each is executed
and then the global one and in the same
way after all works. first local after
all is executed then the global one and
those are handy callback functions hooks
if you want to execute specific code
such as creating a user or deleting a
directory after all as an example you
can delete the directory from um from
the file system uh before all you can
execute a specific artisan command to
run migrations or to create some data in
the database so you can do this inside
these hooks Now I'm going to talk about
data sets and data providers. So if we
want to execute test with a specific
data set, we can use method called with
right here and provide data. And we have
different variations how we're going to
provide the data. I'm going to provide
an array here. Then inside an array I'm
going to provide three elements one,
two, and three.
another set of numbers like five,
seven and 12 and we can continue like
this but that's enough I think. Now the
thing is that pest will take each
element right here in the given uh array
global array this array. So this will be
taken destructured and given into this
function as three variables like a b and
c. So a is going to be one, b is going
to be two, c is going to be three. then
this function will be executed second
time for the second element of this
array. So what we can do is write simple
expect
expect a plus
a + b
to be c just like this. So let's execute
only this test.
PHP artisan
test.
Now look at this. The two tests were
executed basically in the following
function because this is executed two
times for a different data set. So let
me duplicate this. Actually I'm going to
create new one. So let's say we want to
validate multiple emails which we have
taken from a file or from a database.
All right. So I'm going to use test
validates
emails. So then we have a function here
and then I'm going to provide the width
pass an array and then we can take
um let's give it some key like this
going to be anything like email one okay
then we can have an array here this is
going to be zuracample.com
so we can have email two as well and
email two let's say is not a valid
email. It is like this. So we are
providing these two elements inside with
then right here we're accepting an
email. All right. So test will um take
uh actually let's dump let's dd what is
the email here. So we're going to better
understand how it works. So only
we're going to remove this and execute
that. And now look at this. Exactly as
we expect. So pest is smart enough to
take this value when we are expecting
right here only one thing take this
value and take its first element and
give it as a first variable. So if we
have a different elements in the
following array they will be given as
second and third variables right here.
So after this we can write a simple
filter
var like the basic PHP one. We take the
email filter validate email not to be
false. Now if we execute this we expect
uh it to be failed for something for
email two because this is not a valid
email. However if we make this
valid email
then we reexecute this we're going to
see that both are passing. So we also
have possibility to define data set with
a name. As an example I'm going to use
data set users and provide those two
users. Now I can define test
has a valid string as an example
function.
We're going to execute the following
test with users data set. And right here
we're going to expect name. That name
will be Zura for the first execution,
John for the second execution. And then
we can write expect name to be string
and we can execute only
this test.
Actually the other one was executed but
we can see that has a valid string Zura
and John. So this also works. We can
also have uh inside this data set
functions callback functions. I'm going
to define another data set
uh in this case that will be numbers
and provide an array but I'm going to
define a callback functions like fn ar
error functions and we're taking a
random integer or let's simply use rend
uh from 1 to 10 this is the first one I
have syntax error duplicate this and
let's take second one from 10 to 20. So
we have defined this data set. Then we
use test
to test if this has a valid number. We
are executing the following test with
numbers data set.
And we expect each number received right
here to be an integer. So I'm going to
skip this one and skip this one. And I'm
going to execute only this test. Let's
execute it.
What happened?
Now look at this. Uh has a valid number
with with the closure. So this is a
closure. Um but the thing is that this
is tested to be a valid number. And to
show this I'm going to use dump here.
Dump number. We save and execute that.
And now look at this. One and 17. So
those are random numbers uh generated by
the following function. Now let's see an
example of snapshot testing and
screenshot testing. That's interesting
one. So we have one test which snapshots
a homepage and this works in a very
straightforward way. We make request we
get the response and then on that
response content we expect to match
snapshot. So if we execute this very
first time we expect the following
result. Snapshot created at the
following location. There is no previous
snapshot to compare with and to test
this but it created snapshot right now
when we executed this. And if we go
inside test folder pass snapshots
feature snapshot test. We can open this.
And here it contains the entire response
content what was returned. So this is
the current snapshot. Right now this is
a Laravel React single page application.
That's why this is not a typical HTML
serverside response content. Okay. But
if you have a typical like a blog or
serverside rendered content then um the
HTML right here would probably contain
uh like blog posts and so on. So we take
this snapshot and then next time we
execute this and it's going to take that
response content and compare it to the
previously made snapshot and if there is
some difference it's going to report us.
If the snapshot is the same it is going
to simply pass the test. That's a great
test uh to simply check that it
mistakenly you mistakenly doesn't change
the output content. So right now this is
failing because uh as I mentioned this
is more dynamic content and you can see
that here we have some dynamic versions
and things like which which changes on
every refresh basically and that's why
we see a different content but that test
would be ideal to test some server side
rendering um
pages with great SEO. Not the one uh I'm
showing right now. For the one I'm
showing right now, we need to use
browser testing, which I'm going to show
you uh in a second.
Okay, this is also a great way to test
API endpoints. So, we make the request
into our API. Let's say we get the um
user information uh a specific single
user information by ID or something.
Then on the second time, we make the
request and we are sure that we don't
change anything. uh we don't remove
mistakenly some fields from the uh
resource of the user or we mistakenly
didn't add something there. Now let's
see another example which is more visual
regression testing using screenshots.
I'm going to use test here uh and let's
give it the following description
matches homepage
screenshot. Okay. Now we are not using
snapshot as an HTML but we're going to
use screenshot. Okay, I'm going to skip
the above test and I'm going to only
execute this one. We are going to call
visit here provide slash. We're
accessing the homepage and then we are
going to call method assert
screenshot matches. Now, if we execute
this, we're going to see that it is
going to create this snapshot in the
following location. Now, let's bring up
this snapshot and open this matches
homepage screenshot and we can see that
this is how that snapshot looks like.
All right. So now if we execute the same
test again, now we're going to see that
the test is passing because we haven't
changed anything. So, the snapshot was
failing, but this one actually was
passed. Now, let's open welcome.tsx
and let's find this text. Let's get
started, which by the way is displayed
right here. And I'm going to add one one
right here. And we can make this text
red as well. Okay, awesome.
Text red 500. Okay, let's do like this.
Okay, awesome. Now let's bring up
terminal and execute PHP artisan test.
We hit the enter.
Now the test is going to fail. And it
also gives us the following um
suggestion. So if the failing is
expected and if the change we made on
our welcome.dtsx DSX file is
intentional. We can execute the same
command with d-update screenshot and it
is going to take the current version as
this stable version. Otherwise, it
generated difference which we can find
inside browser screenshots image diff
view and this HTML file. If you open
this HTML file, this is a typical HTML
and let's open this in the browser. You
can take this and open in a browser and
let's click on this slider and look at
this. So we have this nice slider view
and here's the difference. Look at this.
Let's get started 111 in red. Previously
we had let's get started in black. So
this is the difference that has been
made and this is pretty cool. Now if I
decide that this one is the stable
version and I want to use this one. Now
I can execute PHP artisan test with
d-update snapshots. it is going to
update snapshots in the in the file
system. The test will not be completed
at all. And the next time if I execute
PHP artisan test, we're going to see
that the test is passed because the
latest version is this what we have
currently and nothing has been changed
after this latest version. Test also
gives us possibility to group our tests
into a specific group and then execute
only that group. As an example, we have
this snapshot test and out pages test.
So what I can do instead of calling this
skip and only. So I'm going to remove
both from here. And let's say I want to
execute only these two tests. I'm going
to assign group to them. And I'm going
to call these snapshots. Snapshots here.
And snapshots here as well. Actually I'm
going to use snapshots here as well. Now
I'm going to bring up terminal. Then I'm
going to execute PHP artisan test dash
group equals snapshots. We hit the
enter. And now only those two tests will
be executed. The first one is snapshot
homepage which was created and the
second is matches homepage screenshot
which was passed. Now if you execute
this second time both should be passed
because the first one actually the first
one is failing. Yeah, we we know why
this is failing because it has a dynamic
content in a response. But the thing is
that we have flexibility to um assign
groups and then execute based on groups.
Actually we also have ability to assign
multiple groups to our tests. So I can
provide an array here. Let's say this is
snapshots and home as well. And this is
snapshots. And we can assign something
else such as screenshots. So we can
execute tests based on one group or the
second group. It's up to you. So if I
try to execute
let's say
screenshots, it is going to execute only
this test.
[100:01] Actually we don't need array here. We
[100:03] can directly provide multiple groups
[100:07] just like this. So let's execute this
[100:09] test and it only executed the last test
[100:13] which was passed.
[100:15] We also have ability to filter tests by
[100:19] the class name of the test or in general
[100:22] by the name of the test. Let me execute
[100:25] PHP artisan test and I'm going to
[100:28] provide d- filter. Inside filter I'm
[100:31] going to provide out pages test. So
[100:35] we're only executing the test which
[100:37] contains out pages test inside the name
[100:40] of it. And as you can see only those two
[100:43] tests are executed. However for both of
[100:46] them we have skip right here and both of
[100:48] them are actually skipped. We can also
[100:51] provide filter equals login and every
[100:55] test that contains a login keyword
[100:57] inside there will be executed. Now the
[101:00] first one is this which was skipped and
[101:03] the second one was it shows a login page
[101:06] from public pages test which was not
[101:10] actually passed. Also when we provide
[101:12] skip right here we can give it custom
[101:15] condition or custom message when it
[101:18] should be skipped or what message it
[101:20] should have. as a message we can provide
[101:22] something custom
[101:26] message
[101:27] and let's execute that
[101:31] and here is the custom message why which
[101:34] should explain why this was skipped
[101:38] we also have ability to create a test
[101:41] and mark it as a to-do as not
[101:44] implemented yet
[101:48] we have some test which we plan to
[101:50] implement but it is not implemented yet.
[101:53] So we give it to-do and when we give it
[101:55] to-do we also have provide to give some
[101:59] custom note.
[102:01] Now we execute that this doesn't have
[102:05] any login keyword and we're only
[102:08] executing tests which has a login
[102:11] keyword inside there um for logging.
[102:14] Let's give it login keyword inside
[102:16] there.
[102:19] Now it is not implemented yet. Login, we
[102:22] have a custom message as well. We can
[102:23] describe that specific test. It is a
[102:27] to-do and we can describe what the test
[102:29] actually should do. But because the
[102:32] functionality is not yet done in some
[102:34] cases, we just know that we will need
[102:38] this test in the future, but we don't
[102:41] have it ready yet because the
[102:42] functionality is not ready yet.
[102:46] Inside the tudo if we follow we can find
[102:48] that it has a different uh parameters as
[102:51] well. So note is one of them but we can
[102:53] assign the user who is going to actually
[102:56] do this test or if the test is specific
[103:00] to an issue we can provide the issue ID
[103:03] as well and then feel we can filter
[103:05] to-dos with a specific assigne or with a
[103:10] specific issue.
[103:12] That's pretty handy. Now I'm going to
[103:13] give you a few examples how you can test
[103:16] your APIs as well which returns always
[103:19] JSON responses. This repository is from
[103:22] my laral API course. There's several
[103:25] hours YouTube video. You can check it
[103:27] out. And the open source the uh code is
[103:30] also in my GitHub open source. You can
[103:32] have a look. So if we open this login
[103:34] API test. So we have several things
[103:37] right here. So first we are setting this
[103:40] throttle key for throttling because we
[103:43] have rate limiting into our API as well
[103:46] and that throttle key is to take the
[103:49] following email and concatenate it with
[103:52] the IP address. Okay. And before every
[103:55] test is executed we are clearing the
[103:58] rate limiting from cache. That's what it
[104:02] does. Now we're going to test if user
[104:05] can login with valid credentials we via
[104:08] API. So we create the user
[104:11] then we are using this post JSON sending
[104:15] the request on API/lo with email and
[104:18] password and we expect the status code
[104:20] to be 200 and we are using the following
[104:23] methods assert JSON. Assert JSON is a
[104:26] prefix. Assert JSON structure to look
[104:29] like this. User should be an associative
[104:33] array or in JSON it's going to be an
[104:35] object containing ID, name, email and
[104:39] there should be token as well alongside
[104:41] with user and we also expect the actual
[104:45] JSON to contain user with ID this name
[104:49] this email this and this is the user
[104:51] what we just created another test and
[104:55] also we have an one more expectation
[104:57] here also we expect the response JSON
[105:01] token not to be empty. After this, we
[105:04] also expect the user to be
[105:06] authenticated.
[105:08] All right. User cannot login with
[105:11] invalid email via API. We create the
[105:13] user and then we send the request with a
[105:18] wrong email but the correct password. We
[105:20] expect the status code to be 422
[105:24] and we expect inside the JSON to be
[105:27] validation errors on key email and we
[105:30] expect the user to be guest.
[105:34] Okay, another test user cannot login
[105:36] with invalid password via API. So we
[105:40] create the user we use now correct email
[105:42] but the wrong password and we expect
[105:44] something similar 222 and assert JSON
[105:48] validation errors user needs to be
[105:50] guessed login requires email field. Now
[105:53] we don't provide email and we expect
[105:55] there to be um validation error on email
[105:58] field. Now we don't provide password and
[106:01] we expect there to be validation error
[106:03] on password. Now uh login requires valid
[106:08] email input. So we don't provide actual
[106:10] email. It's an invalid email. And we
[106:12] expect there to be uh the following
[106:15] validation error. Login is rate limited
[106:17] after too many attempts. This is
[106:18] interesting one. We create the user and
[106:20] then we try five times um login request
[106:25] with wrong password. After this, even if
[106:29] we try with correct password because
[106:33] this is the sixth attempt, we expect
[106:35] there to be a validation error on email
[106:38] and login works after rate limit
[106:41] expires. Again, we have the user, we
[106:44] make five failed attempts. Then we clear
[106:48] this throttle key and then we make the
[106:51] request with correct password and we
[106:53] expect the user to be authenticated. And
[106:56] the final one is login endpoint requires
[106:58] guest middleware as well. We have the we
[107:00] create the user and we are acting as
[107:03] this user with the sync town guard. So
[107:06] we are sending a request as already
[107:08] authenticated user and the request goes
[107:11] into API login with the correct email
[107:14] correct password and we expect assert
[107:16] status to be 302 redirecting.
[107:20] Okay, we have one more login response
[107:22] contains correct user data structure. We
[107:25] create the user. We send the request and
[107:28] this is something we have already tested
[107:29] above but slightly different way.
[107:33] So we expect the JSON to contain user
[107:36] with the following data and also we can
[107:39] use this expectations on the response
[107:40] data. We expect the response data user
[107:43] not to have key password and not to have
[107:47] key remember token because those are
[107:50] sensitive information and we should not
[107:52] return. Now if we execute PHP artisan
[107:55] test
[107:57] we are going to see all tests are
[107:59] passing.
[108:02] Now we're going to have a look at a real
[108:04] project and create tests for a real
[108:07] project. This is Laravel medium clone
[108:10] which I created for free code camps
[108:12] Laravel course video. You can check it
[108:14] on free code campams YouTube channel.
[108:16] And this is the project. So it is a
[108:19] typical medium clone where we have
[108:21] registration login. If we are opening
[108:24] this as a guest user we are seeing
[108:26] several published posts uh with the um
[108:30] like sorted in a specific order. If we
[108:33] authenticate
[108:35] so as an example then on my homepage I
[108:38] see um my posts actually the posts of
[108:42] the users I am following too. And if we
[108:44] click on the post, this is post posted
[108:47] by Nunaduro and I am following Numaduro.
[108:51] If I click on this, this is posted by me
[108:53] and actually I'm following myself as
[108:55] well. This is allowed based on the
[108:57] application. And we have ability to
[108:59] unfollow from here to click on the
[109:02] user's profile page
[109:04] to go back to clap the post or unclap
[109:07] it. And if we go back and click on
[109:09] Numaduro click on here, we also see
[109:11] unfollow button right here as well. And
[109:14] we see a dedicated posts right here for
[109:17] that specific user. Okay. And we have
[109:20] filtering ability as well for um for
[109:24] articles. And now let's have a look at
[109:27] what tests are available in this
[109:29] project. This is created a year ago
[109:32] before pest 4 was released. And we have
[109:34] to update this into pest 4. And then we
[109:37] have to create a real test. So this is a
[109:39] real world scenario. Let's go into test
[109:44] folder and I'm going to expand this unit
[109:47] test. There's nothing important. So I'm
[109:49] going to delete this example test. Now
[109:51] let's open this feature example test
[109:54] again. Something we can delete. We have
[109:57] the profile test and inside
[109:58] authenticated folder. This is
[110:00] interesting. Let's open this
[110:02] authenticated folder and have a look.
[110:06] So it doesn't use browser testing. It is
[110:09] using typical test three testing with
[110:11] this get in most of the cases. So it
[110:14] simply makes sure that the login screen
[110:16] can be rendered a pretty basic example.
[110:19] Then it makes sure that user can
[110:21] authenticate using login screen. It
[110:24] creates the user then sends the post
[110:26] request on here. Then checks the user is
[110:28] authenticated and the user needs to be
[110:31] redirected into dashboard page. Here we
[110:34] have another example. User cannot
[110:36] authenticate with invalid password.
[110:38] Another one users can log out. We create
[110:41] the user. We are acting as the just
[110:44] created user and then we perform log out
[110:47] and we check the user is guest and we
[110:50] should be redirected into slash. Okay,
[110:52] nothing major but now let's have a look
[110:55] inside email verification test. This is
[110:57] interesting. So we check that email
[111:00] verification screen can be rendered and
[111:02] this happens when we create a user as an
[111:05] unverified user. This unverified is a
[111:09] method inside user factory which makes
[111:11] sure that email verify that is null. But
[111:14] our application requires user to be
[111:16] verified. So we are acting as just
[111:19] created user and accessing the page
[111:22] verify email. And if this returns with
[111:25] 200 status code, then the email
[111:27] verification screen can be rendered. Now
[111:29] it's getting more interesting here. Now
[111:32] we're checking here if email can be
[111:34] verified. Those tests by the way are
[111:36] coming were coming uh from a default
[111:40] laral installation at the time when I
[111:42] created this project. Okay. So we create
[111:44] unverified user and we are faking
[111:48] events. So we have ability to fake
[111:51] certain things such as events,
[111:53] notifications, mails and so on. So we
[111:56] are faking events. After this we are
[111:59] creating temporary signed wrote in a
[112:02] similar way how it is done when the
[112:05] email verification is sent. So we are
[112:08] using the following uh wrote name. We
[112:11] are using the following um code to
[112:15] simply add 60 minutes to now to make
[112:19] this link valid for 60 minutes. And then
[112:21] we are taking ID and hash uh which will
[112:25] be the hash of user email and now we
[112:28] have this verification URL. After this
[112:30] we are acting as just created unverified
[112:33] user and we are accessing that
[112:35] verification URL after which we expect
[112:38] user to be verified. So event verified
[112:42] should be dispatched. After event
[112:44] verified is dispatched then we expect
[112:46] the user and we are calling this fresh
[112:49] to simply reselect the user from the
[112:52] database. And after this we expect that
[112:54] user has verified email to be true. And
[112:58] we also expect that user to be
[113:00] redirected into dashboard with verified
[113:04] one because we just accessed this email
[113:07] verification URL. Okay. Okay, now let's
[113:09] have a look if email is not verified
[113:12] with invalid link. So here we
[113:14] constructed this verification URL with a
[113:16] wrong email. We access it and we expect
[113:20] user has verified email to be false.
[113:23] Okay, that's all about email
[113:25] verification test. Now let's see another
[113:27] interesting example inside password
[113:30] confirmation.
[113:31] So uh I think this is the basic one. So
[113:34] we create the user, we are acting as
[113:36] that user and accessing the password
[113:37] confirmation. That's basic. Uh where's
[113:40] the one I was looking for? Here we are
[113:42] accessing with um the correct password.
[113:45] We are simply uh confirming the password
[113:49] and we expect the redirect with no
[113:51] errors. Um and here we simply use a
[113:54] wrong password. Okay, that's not the one
[113:56] I wanted to show to you. Password reset
[113:58] test. This is it. Okay, let's start from
[114:01] here. This is basic one and this is the
[114:04] notification fake. Interesting one. So
[114:06] we reset the password link uh we test
[114:09] that the reset password link can be
[114:11] requested and here we are faking the
[114:13] notifications because when we try to
[114:16] reset password link we expect an email
[114:18] to be received through notifications
[114:20] right so we are faking notification we
[114:23] are creating the user we are sending
[114:25] post request into a forgot password with
[114:28] this email and through this notification
[114:32] class we test with assert sent two we
[114:36] test that notification
[114:39] this notification reset password was
[114:41] sent to the following user that's
[114:44] interesting now also reset password
[114:47] screen can be rendered and this is
[114:49] interesting one so we send post request
[114:52] to forget password
[114:54] uh we check that notification was sent
[114:57] to the following user but after this
[115:01] from this notification we get the token
[115:04] we use that token to access to the
[115:06] following URL and we check that uh the
[115:10] following URL returned with 200 status
[115:13] code which means the notification was
[115:16] sent and we we are able to click on that
[115:19] password reset link and it worked and
[115:23] another example
[115:26] password can be reset with a valid
[115:28] token. Now again we created user we
[115:32] performed for password we received the
[115:35] notification and then using that
[115:37] notification token we performed password
[115:41] change we send the post request to reset
[115:44] password we provide the token email and
[115:46] new password and we expect that session
[115:49] has a no errors and we are redirected to
[115:52] the login page that's pretty interesting
[115:54] one also we have this password update
[115:56] test which is I think basic compared to
[115:59] others. We create the user acting as the
[116:01] user accessing the profile page and
[116:03] putting the uh current password as well
[116:05] as the new password. We expect no errors
[116:08] redirected into profile page and we also
[116:10] check that the user's password right now
[116:14] is the same as the new password plain
[116:18] text to be um like hashed with a
[116:22] specific hash facade.
[116:25] Okay. Uh another one correct password
[116:28] must be provided to update the password.
[116:30] If we provide the wrong password here we
[116:31] expect the session has an error on the
[116:33] update password and um this is the
[116:36] field. Okay. And the last one is
[116:39] registration trait. uh we access the so
[116:43] this is the interesting one because this
[116:44] is one is failing because we I in the
[116:47] project updated the registration part
[116:51] and yeah we're going to fix this and we
[116:54] have this profile test as well which
[116:56] doesn't have anything significant but
[116:58] the thing I wanted to show to you was uh
[117:01] this notification fake okay we don't
[117:04] have anything interesting here as well
[117:06] now let's bring up terminal and I'm
[117:09] going to execute PHP P artisan test and
[117:13] we're going to see that some of the
[117:15] tests are failing. Those tests are
[117:17] related to profile information can be
[117:20] updated. New user can be registered. So
[117:24] in our
[117:26] website now the registration
[117:30] let's access this registration now has
[117:32] username by default the username was not
[117:35] there. I haven't touched the test when I
[117:38] developed this project. So username is
[117:40] missing. If we open right now
[117:44] the registration test right here, we're
[117:48] going to provide the username. Let's
[117:50] give it username to be test.
[117:55] Username to be test. Now if we execute
[117:59] this again,
[118:02] now we don't see error on new user can
[118:07] register anymore. We have error. Profile
[118:09] information can be updated. Let's go
[118:11] into profile test and search for this
[118:16] profile information can be updated.
[118:20] We provide the name. We should provide
[118:24] username as well to be test. So we save
[118:27] this.
[118:29] Uh actually we have one more error.
[118:31] Let's fix this as well. Email
[118:32] verification status is unchanged when
[118:35] the email address is unchanged.
[118:39] Before we fix this, there are two things
[118:41] to change. First, we're going to change
[118:43] the database configuration which is used
[118:45] by pest. And second, we're going to
[118:47] update pest framework into pest version
[118:50] 4. Let's open
[118:53] phpit xml.
[118:56] Okay. Now look at this. By default in my
[118:59] project which is created like about a
[119:02] year ago this part is commented out
[119:05] which means that this is using the exact
[119:07] same database I have right now in this
[119:11] project. So as soon as I execute the
[119:12] test it is working on the same database.
[119:14] So my data is lost right now. And this
[119:18] is kind of annoying because I just
[119:20] created uh several useful posts.
[119:24] Okay. Okay. So, first we have to
[119:25] uncomment this.
[119:27] Make sure it is going to run uh inside
[119:30] memory always. Then let's update into
[119:33] latest version of P uh PS4 and then we
[119:37] can work on fixing the test and creating
[119:39] new tests. So, if we have a look inside
[119:41] composer JSON, we have the PPHP version
[119:44] 3 something. Let's change this into 4.1.
[119:49] And we have to update as well the pass
[119:52] plugin Laravel into 4
[119:55] zero.
[119:58] Okay, we're going to save this. Let's
[120:00] execute composer
[120:03] update. Test4 has been installed and
[120:06] we're going to fix the following part.
[120:07] We're going to provide username to be
[120:10] test as well. Let's bring up the
[120:12] terminal and execute PHP artisan test.
[120:16] And we see all tests are passing.
[120:20] Now I'm going to install browser testing
[120:22] in my
[120:25] project not install browser but I'm
[120:28] going to install b
[120:30] plug-in browser as a dev dependency.
[120:33] It's going to download this uh playright
[120:36] as well. This was installed which is
[120:39] great. And now we're going to create new
[120:41] test and I'm going to execute phpart
[120:43] artisan make test. I'm going to create
[120:46] this inside post folder. And let's call
[120:49] this post test. The test was created.
[120:53] Let's open post test. Here we go. And
[120:57] I'm going to define several tests right
[120:59] here. Let me clear this up. So I'm going
[121:02] to define test that user
[121:07] can create a post. So that's one thing.
[121:11] Let's assign to-do to it. Next,
[121:16] we are going to write test to check if
[121:20] the user can view published post. User
[121:24] can view published post.
[121:29] Let's assign to-do to it as well.
[121:32] Another one is going to be uh user
[121:36] cannot edit other users post.
[121:42] assign to-do to it. And now another one
[121:46] is going to be user can update its own
[121:49] post. User can update
[121:53] uh their post.
[121:57] Okay. Awesome. And the last one I'm
[122:00] going to do is to test if user can
[122:02] delete
[122:05] user can delete their post.
[122:11] Okay, that's enough. Uh, actually, let's
[122:13] add one more thing. Uh, on the homepage,
[122:17] we display posts for the
[122:21] uh from the following users. Basically,
[122:23] I am following too. So, um let's let's
[122:28] do the last one as a as a browser test.
[122:33] Okay, which we will be able to debug
[122:34] this in the browser as well. Um,
[122:37] dashboard shows posts from followed
[122:43] users.
[122:45] Okay cool
[122:48] to do. Okay, awesome. Now, let me bring
[122:50] up terminal and I'm going to execute PHP
[122:52] artisan uh test dash filter to be post
[122:58] test.
[123:01] Post test. Okay, hit the enter. Now we
[123:03] see all to-dos right here. Awesome. Now
[123:05] let's start implementing from the first
[123:07] one.
[123:09] Okay. So we have to create the user and
[123:13] then we have to try to create new post
[123:16] using that user and we have to attach
[123:19] the information um such as image title
[123:22] content and few other things inside that
[123:25] form. Okay. So I'm not going to do this
[123:28] as a browser test. Uh I'm going to do
[123:30] this as a typical submission test uh
[123:34] like it it would work in test three as
[123:36] well. So we're going to start with this
[123:41] acting as so we're going to act as the
[123:44] authenticated user but we have to create
[123:45] that user. So user equals user factory
[123:50] create. Let's import this user.
[123:54] Okay. Awesome. Now we are acting as this
[123:57] user. Actually we will need this user in
[124:00] couple of other tests. So I'm going to
[124:02] define before each and I'm going to
[124:05] define this user equals user factory
[124:08] create. Okay, awesome. Now I'm going to
[124:10] provide this user right here. So I'm
[124:12] acting this user which I just created.
[124:16] Then I'm going to send post request into
[124:20] the following route. The route is going
[124:22] to be post store and I'm going to send
[124:24] the following information. I'm going to
[124:26] provide the title which is going to be
[124:29] um test post title. Okay. Next, I'm
[124:35] going to provide content which is going
[124:36] to be test post content. Let's provide
[124:40] also category ID. Category ID is going
[124:44] to be something. Uh you know what? We
[124:48] can create new category right here. But
[124:51] uh I'm going to execute this seed
[124:54] command and it is going to seed several
[124:58] categories
[125:00] uh inside the database.
[125:02] If we open database cedar we're going to
[125:06] see that we are creating the following
[125:07] categories in the database when the seed
[125:09] comment is executed. So I'm going to
[125:11] execute the seed command before every
[125:14] test. So inside category id actually and
[125:17] whenever I do this uh I want to select
[125:19] one category and assign this. So this
[125:24] category equals category not factory
[125:27] create but instead category
[125:30] limit one
[125:34] first. Okay. Now we have the category
[125:37] and we're going to take this category ID
[125:41] and assign this in the form. Then we
[125:43] have to take image as well. So we have
[125:46] we have the image and actually let me
[125:48] define a fake image here. Image equals
[125:52] uploaded file. We're going to call fake.
[125:56] Then we're going to call image to create
[125:58] that fake image. And then optionally we
[126:01] can provide some extensions as well such
[126:03] as uh width to be 800, height to be 600.
[126:07] Uh let me replace this with import.
[126:11] Awesome. So we're going to take this
[126:12] image and assign here provide here and
[126:17] finally we have to provide published it
[126:20] at published it at will be now okay so
[126:25] we're making post request on the
[126:26] following URL and now what do we expect
[126:30] so we can change several methods or we
[126:32] can save a response in a variable and
[126:34] then we can use this method on the
[126:36] variable so let me change the methods
[126:39] assert we expect redirect to happen on
[126:43] dashboard page. So we expect assert
[126:46] redirect
[126:48] um wrote is going to be dashboard
[126:52] and that's it. Okay. When we are
[126:55] redirected on the homepage then we can
[126:58] check in the database if that post is
[127:02] available in the database. So there are
[127:05] two ways actually or multiple ways to
[127:07] test if the post was actually created.
[127:10] One is that we can check the database.
[127:11] Second is that uh after this we navigate
[127:14] into my post page and we check if the
[127:18] post is there with the same title. We
[127:21] can do this as well. So now let me do
[127:23] the database check. So we're going to uh
[127:25] call assert database has and we are
[127:29] checking inside posts table. We are
[127:32] looking for a post which has title test
[127:36] post title. All right. And that should
[127:41] have user
[127:43] ID uh that should have user ID current
[127:46] user ID which is authenticated right now
[127:49] and that should have category ID the
[127:51] current category what we assigned. So if
[127:54] this is completed then we know that the
[127:56] test is completed and the post was
[127:59] created with image and everything.
[128:02] Okay. So let's bring up terminal
[128:05] and
[128:06] execute PHP artisan test d- filter post
[128:10] test. Okay, this is still to-do. So we
[128:13] have to remove this to-do. Re-execute
[128:15] the test and we see that was failed.
[128:19] Post store. It is not in plural form to
[128:22] uh that is in a singular form. Post dots
[128:26] store. Okay, let's reexecute now.
[128:31] Let's see. the post store.
[128:34] I said it is singular.
[128:39] Okay.
[128:41] Now there's an uh can attempt to read
[128:43] property ID on a null. It seems like we
[128:46] have an issue on category because I did
[128:49] something very stupid.
[128:51] Why didn't you tell me that I was doing
[128:53] this stupid thing? Okay. So, first seat,
[128:56] then select create the user and then um
[129:02] then create select category. Okay. Let's
[129:04] execute this one last time. Okay. And we
[129:06] see that the test is passing. But here's
[129:09] one interesting thing which I'm going to
[129:11] which I'm going to add into this test.
[129:14] So if we open postphp
[129:18] uh which is a model that model uh
[129:21] implements has media has media is a
[129:24] spatis package where is this has media
[129:27] it is imported from spatiy media library
[129:30] and the thing is that
[129:33] it automatically
[129:36] converts that uploaded image into
[129:39] different resolutions if the cues uh if
[129:42] we are listening to cues and the cues
[129:44] are running um in the background. So we
[129:48] are registering this media conversions.
[129:50] So
[129:52] this parties package will take the image
[129:55] and convert them into preview and large
[129:58] versions and we also uh have this media
[130:01] collection and everything is added
[130:03] inside this media collection. So
[130:05] internally cues are working.
[130:08] So what we can do is test if Q
[130:13] processing happened as well inside the
[130:17] following test. For this we have to fake
[130:20] Q's. So I'm using this illuminate
[130:23] support facads Q and then I'm going to
[130:25] call fake on that. And let me replace
[130:28] this with import. And so I'm faking Q's
[130:32] and at the end when the test is
[130:34] completed I'm going to use that Q and
[130:38] I'm going to call assert
[130:40] uh assert pushed. Assert pushed means
[130:44] that the job has been pushed and the job
[130:47] is called uh perform conversions
[130:52] job and that perform conversions job is
[130:56] coming from spatis media library
[130:58] package. So I'm going to import this as
[131:00] well. And now let's test this again. And
[131:04] as you can see um the test was passed.
[131:08] Now if we go into post and if we remove
[131:12] this implements media
[131:14] also remove this interacts with media.
[131:19] Now these methods doesn't make any
[131:21] sense. All right. And the job should not
[131:25] be processed should not be started. If
[131:28] we open terminal right now and test
[131:30] this. Now the test was failed.
[131:36] Add media from request. So this is now
[131:39] this is actually failing uh from a code
[131:42] level not from the um from the test
[131:46] perspective. If we scroll up actually
[131:50] I just want to give you that
[131:54] information
[131:57] I'm using this inside post controller
[132:01] right
[132:02] we take this image and add um uh add
[132:06] media from request and that happens on
[132:08] store. So if I comment out this
[132:12] then bring up terminal
[132:15] and execute that. Now look at this. The
[132:20] expected spatier media library
[132:22] conversions job perform conversions job
[132:24] was not pushed failed. Asserting that
[132:28] false is true. This is exactly what I
[132:30] wanted to show to you. I'm going to
[132:32] revert this. I'm going to also add this
[132:35] interacts with media and implements has
[132:38] media as well. So we can obviously
[132:40] remove this Q thing and test this
[132:43] without that as well. But we are also
[132:45] testing that Q's are pushed or we can
[132:48] create a separate test exclusively for
[132:50] these cues. Okay, now we have the first
[132:53] test ready. The second test is that we
[132:56] can check if the user can view published
[132:58] posts. Right? So we're going to create
[133:02] the function here. Again, we already
[133:04] have the user created. So we don't need
[133:06] to create the user. However, we're going
[133:09] to create the post. So post equals post
[133:14] post
[133:16] factory
[133:19] factory.
[133:21] Uh let's import post
[133:25] factory. Let's call create.
[133:28] And we're going to provide um user ID
[133:31] which is going to be this user ID. We're
[133:35] going to provide category ID which is
[133:38] going to be this category ID. And we're
[133:41] going to provide published ad which is
[133:43] going to be now let's subtract something
[133:46] subday one. Okay. Now we have a post
[133:50] and now we're going to try for the
[133:52] current user to view that post. So this
[133:57] get
[133:59] we're going to use the following road
[134:01] post show providing uh we're going to
[134:04] provide two things inside post show. If
[134:07] we open web.tphp and search for post.sh
[134:10] show this accepts two arguments username
[134:14] and post slug. So we have to provide
[134:17] both. So I'm going to provide user name
[134:20] to be this user's username and we have
[134:23] to provide post which is going to be
[134:26] this post slug.
[134:29] Okay, awesome. Now I'm going to move
[134:32] this down. Now we send the request, we
[134:35] got the response. Now we can save this
[134:38] into a response variable and then we can
[134:41] call assert methods on that or we can
[134:43] chain some methods like assert okay is
[134:47] one method. We can also assert C post
[134:51] title and we can also assert C post
[134:56] content. So the we fetch the post and we
[135:00] expect to see that post information.
[135:03] Okay. Awesome. Um I think we should
[135:07] remove this to-do and we should execute
[135:09] this test. And we see two tests are
[135:12] passing. The third one is that user
[135:14] cannot edit other users post. Let's
[135:19] write this as well.
[135:22] Oops. Let's remove the to-do. So we have
[135:25] a user already created, right? But right
[135:28] now we're going to create a new post and
[135:31] we're going to create new user.
[135:36] So let me create first owner post owner
[135:39] user which is going to be user factory
[135:42] create
[135:44] user factory create. Okay. Next I'm
[135:46] going to create a post and I'm going to
[135:49] create this post. I don't want the full
[135:53] autocomp completion. I want only small
[135:56] part to be able to complete it. I don't
[135:58] want too many code to be written at the
[136:00] same time. Uh I want you to follow me.
[136:03] So we are calling this post factory
[136:06] create and we're going to provide the
[136:08] user ID which is going to be owner ID
[136:11] and we're going to provide also category
[136:14] ID as well which is going to be this
[136:16] category ID.
[136:18] So we have the owner user. We have this
[136:22] user which is at the top. Let me
[136:25] collapse the other tests.
[136:28] And now we're going to try if this user
[136:31] can edit the post of this owner user.
[136:34] All right. So we're going to use this
[136:37] acting as
[136:43] by the way.
[136:47] So right here in the following part we
[136:49] tried to access a post show without the
[136:52] authenticated user. We don't have acting
[136:55] as which is also not a problem. So we
[136:58] tested if guest users can view posts
[137:01] which is logical. Okay we can leave
[137:03] this.
[137:04] So right here we're acting as this user
[137:07] and we are trying to access we're trying
[137:12] to access and get uh the post
[137:16] information and we expect assert
[137:19] forbidden.
[137:21] I think this looks good. So we have the
[137:24] owner user create the post and then
[137:26] using this user we act and try to get
[137:28] okay let's execute the test and the test
[137:31] has been passed. Okay awesome. Now we're
[137:34] gonna see if this user can update their
[137:37] own post
[137:41] function.
[137:44] Okay.
[137:47] Now we are going to use again Q fake
[137:49] here because when we attach an image
[137:52] again that image is processed. So at the
[137:55] end we want Q
[137:58] assert
[138:00] pushed and that's going to be perform
[138:02] conversions job. Okay. Now here we are
[138:06] going to create a post post equals
[138:10] post factory create and we have to
[138:12] provide user ID and we have to provide
[138:15] category ID. Okay, anything else doesn't
[138:17] matter. Then I'm going to create new
[138:20] image right here which is going to be
[138:22] uploaded file fake image. Let's give it
[138:26] name test jpg. And then we're going to
[138:29] send the request. So we're going to use
[138:31] this
[138:33] acting as this user. Then we're going to
[138:36] use put method to send the request on
[138:39] post update. And we're providing right
[138:42] here
[138:44] post slug. So we try to update actually
[138:48] let's have a look inside web.php PHP
[138:50] inside post update. This is it. Okay. So
[138:55] just providing post ID or post will be
[138:57] enough. So we can simply provide post
[139:00] here. And inside the data we're going to
[139:02] provide title updated title. We're going
[139:05] to provide content to be updated
[139:07] content. We're going to provide category
[139:09] ID and we're going to provide image.
[139:12] Okay. And once the update is performed
[139:15] then we can write assert redirect
[139:17] dashboard. We expect the user to be
[139:19] redirected uh into dashboard or we are
[139:22] is the user actually redirected. I think
[139:24] the user is redirected into my posts
[139:28] page as I remember. So let's try this.
[139:32] And once that is done then we need to
[139:35] check in the database assert database
[139:39] has a posts and we're going to search
[139:41] this
[139:43] with a new title.
[139:46] This is going to be the post ID should
[139:49] be the post ID
[139:52] but the title is interesting. The title
[139:54] needs to be updated title as we provided
[139:57] right here. Okay, if this is done then
[139:59] the update works. Now let's execute
[140:01] this. User can update the air post.
[140:05] Okay, awesome. The next one is user can
[140:08] delete their post. I think this is
[140:09] pretty straightforward. So I'm leaving
[140:12] this to you as a challenge. implement
[140:15] this right now and then come back and
[140:17] see my solution.
[140:20] Okay, so I hope you made it. In order to
[140:23] delete the post, first we have to create
[140:25] this. So post factory create. We're
[140:27] going to provide the user ID and we're
[140:29] going to provide category ID. Others we
[140:32] don't care. Now we're going to use this
[140:35] acting as this user and then we're going
[140:38] to use delete
[140:40] on post destroy. So we're sending delete
[140:44] request on post destroy sending the post
[140:47] and that's basically it. So once we do
[140:50] this
[140:52] assert redirect to my post we expect to
[140:55] be redirected to um my post page.
[141:00] Okay. And also we're going to check that
[141:03] the post doesn't exist in the database.
[141:05] So this assert database missing inside
[141:09] posts table. This ID must be missing. So
[141:14] let's execute this.
[141:17] Something was failed.
[141:19] User can delete their post. Assert.
[141:22] Okay. So we expected to redirect into my
[141:24] post page, but we are redirected into
[141:27] dashboard. Okay. I don't know why I did
[141:29] this, but we should uh write a test
[141:32] according to how it is done. So we
[141:35] expect to be redirected on dashboard.
[141:38] Okay. Awesome. Now the test is failing.
[141:41] So if at some point I go into post
[141:44] controller and decide that after
[141:47] deleting my record I want to be
[141:49] redirected into my post page
[141:52] then my test will fail. Okay. So I'm
[141:55] going to return this into dashboard. And
[141:57] the final test I want to write as a
[142:00] browser test. So it's going to be um
[142:03] interesting. Let's go into pestph
[142:07] uh post test php file and let's write
[142:10] this function here. So here this is a
[142:14] complex chat uh test a little bit
[142:15] compared to others. So on dashboard
[142:18] we're going to test if the current user
[142:20] sees posts from followed users. All
[142:23] right. So first I'm going to create
[142:30] uh two users.
[142:33] One is going to be followed user
[142:38] and second is going to be
[142:41] unfollowed
[142:43] user.
[142:45] Unfollowed
[142:47] user. Oh why do you have autocomplete
[142:50] all 10 lines? I don't want that.
[142:53] I want to write step by step. So we have
[142:56] two users followed users and unfollowed
[142:58] users. And we have this user as well.
[143:01] Okay. So then inside this user
[143:07] um inside this user following
[143:11] we are going to attach
[143:14] uh following users ID
[143:21] following. So this user now is following
[143:24] this user. All right. So next we are
[143:28] going to create
[143:30] post
[143:33] post
[143:35] factory
[143:37] create.
[143:38] We're going to provide the user ID which
[143:41] is going to be
[143:43] unfollowed users ID. So we are creating
[143:46] the post for unfollowed user. Actually
[143:49] we have to create two posts to be more
[143:51] specific. So let's create first followed
[143:54] user ID. We have to provide the category
[143:56] ID and we have to provide published
[144:00] published add to be in the past
[144:04] comma is missing here. Okay. So this is
[144:08] followed post
[144:11] followed
[144:13] post. Now we're going to create
[144:15] unfollowed users post. So this is going
[144:18] to be unfollowed user post. Unfollowed
[144:22] post.
[144:26] After this, let's use now browsers API
[144:30] uh and we're going to visit dashboard.
[144:35] We don't want this absolute. So, I'm
[144:36] going to remove this. So, we're going to
[144:37] visit dashboard. We're going to
[144:41] uh let me think.
[144:48] Okay. So, we need to authenticate.
[144:53] we need to authenticate as this user and
[144:56] only after this we should be able to see
[144:59] a post from this followed uh follow
[145:04] followed user this post basically. So
[145:08] now let's try to access login page first
[145:11] and then we have to fill up some
[145:14] information like we're going to fill up
[145:16] the uh this user email and we are going
[145:22] to fill password
[145:26] which is hardcoded password inside the
[145:29] user factory. So that's the plain
[145:31] password. After this, we're going to
[145:33] press on
[145:36] log in and we expect assert path to be
[145:44] slash. So we expect to be redirected on
[145:46] slash. After this we expect to see
[145:50] followed post title
[145:53] and we expect assert don't see
[145:58] assert oh semicolon assert don't see
[146:03] unfollowed post title so I think that's
[146:05] the complete test and let's execute this
[146:10] so and I'm going to provide d-debug
[146:14] to see this.
[146:20] Okay, it was so fast. Uh, okay. Let's
[146:24] write debug method here.
[146:28] Now, let's execute this without debug.
[146:36] Okay, here we go. So, we are redirected
[146:38] into dashboard and we see the following
[146:41] post and in the background. Press any
[146:43] key to continue. And we see that the
[146:45] test was passed.
[146:48] That is really good. Now let's execute
[146:51] this without stepback.
[146:56] And we're going to see
[146:58] six tests are passing.
[147:02] Here are new tests. We have clap test
[147:04] inside which we test if the user can
[147:07] clip and uncip post and guest user
[147:10] cannot clip the post. We have similar to
[147:13] test user can follow and unfollow
[147:15] another user and user guest user cannot
[147:18] follow the user. We have the dashboard
[147:20] integration test as well uh which simply
[147:24] checks that on dashboard uh it tests the
[147:28] dashboard basically with following and
[147:30] unfollowing. We have the profile image
[147:32] test, public profile test as well and we
[147:34] have security test. You can find all
[147:36] these tests in the project's repository.
[147:39] The link will be in the video
[147:41] description. Let's review two tests
[147:43] which I feel is interesting. So we
[147:45] create the user, we create the category.
[147:47] We can of course put this inside the
[147:49] before each as well. You can you can do
[147:51] this. So we create the user, we create
[147:53] the category and then we create the
[147:55] post. Then we are acting as this user
[147:57] and we're sending post request to the
[147:59] clip wrote and passing the post and we
[148:02] expect this to return with 200 status
[148:05] code and we expect it to return JSON
[148:08] which should have clips count equals one
[148:12] and in the database we also expect that
[148:15] inside clips table there should be a
[148:17] record for the user ID and post ID.
[148:20] After this we're acting as the same user
[148:22] sending the same request on the
[148:24] following URL and we expect the clips
[148:26] count to be zero and in the database
[148:30] there should not be any record with
[148:32] clips. So in the exact same way we are
[148:34] doing following and unfollowing.
[148:36] However, if we try to uh clap as a guest
[148:40] user and send this clap uh post request
[148:44] on the clap road, we expect to be
[148:46] redirected on the login page. Now
[148:48] another interesting one is this
[148:50] dashboard integration test.
[148:53] So we create the user and we create two
[148:56] other users for following and we create
[148:58] the category as well. So inside the user
[149:01] following we attach those two users. So
[149:03] this user is following these two users.
[149:07] So after this we are creating several
[149:11] posts. All right. Then we take the five
[149:15] post and iterate over those posts and we
[149:19] are assigning follower followed user one
[149:23] or followed user two. So we are updating
[149:26] those posts.
[149:29] After this we are making requests on the
[149:31] dashboard page. We expect it to have a
[149:33] status code 200 and we expect the view
[149:37] on this dashboard page to has posts and
[149:40] the post count must be more than zero
[149:43] and the first post should have loaded
[149:47] data uh for user as well as for media.
[149:51] So we simply check that we see
[149:53] information on the dashboard page and
[149:56] posts are rendered properly. As an idea
[149:59] I'm going to give you a hint. So we
[150:01] could do something similar but instead
[150:03] of doing this. So we can do the
[150:06] following. We can access using visit
[150:09] method to the dashboard page and we can
[150:12] count how many posts we see on the
[150:15] dashboard page and we can do this using
[150:17] browser's API. Now let's take this
[150:20] project and deploy this on production
[150:22] environment and we're going to also set
[150:24] up CI/CD pipelines where our running
[150:28] tests command will be inside the
[150:30] deployment phase and never and if the
[150:33] tests are failing our application will
[150:35] not be deployed as well. So let's go
[150:38] into hosting age panel and I have
[150:41] grabbed the following hosting. When you
[150:43] create new account you're going to see
[150:46] the following screen. So we have to
[150:47] create or migrate website. I'm going to
[150:49] create website. Click on next. I'm going
[150:51] to choose this empty phtml website. And
[150:54] now I can grab new domain. Let me search
[150:58] for laravelesting.io.
[151:03] That domain is available for me. So I'm
[151:05] going to grab that and I'm going to
[151:06] click on next.
[151:10] Because I am registering a new domain, I
[151:12] have to enter some of the contact
[151:14] details of me, my personal contact
[151:16] details. So I'm going to choose this and
[151:18] click on finish registration.
[151:20] Until this is being registered, let me
[151:22] introduce my open source package using
[151:24] which we're going to deploy this project
[151:27] to Hostinger's shared hosting and this
[151:30] really helps us in the deployment phase
[151:32] and just takes away a lot of manual
[151:35] work. So make sure you grab the
[151:37] following code, open your project, paste
[151:40] and hit the enter. We're going to
[151:41] install this as a dev dependency.
[151:44] After this we're gonna configure the
[151:46] following in variables. This is
[151:48] necessary our computer to connect to
[151:51] hostinger hosting and um make some
[151:54] operations there and this is necessary
[151:56] our repository to connect to GitHub API
[151:59] and generate the deployment key as well
[152:01] as secret tokens for automatic
[152:03] deployment. So I'm going to get this.
[152:06] Next I'm going to open
[152:10] and I'm going to paste this at the very
[152:11] bottom. This package is installed. Then
[152:13] let's open Hostinger and we have to
[152:15] choose the location at this stage and
[152:17] click on next. Now it is going to
[152:19] initialize our hosting for us. Once the
[152:22] hosting is initialized, we can try
[152:24] opening this laral testing.io. But we're
[152:27] going to see the following error. That's
[152:29] because the SSL certificate needs to be
[152:31] installed. Even if we try to open this
[152:34] under HTTP,
[152:36] it still is in connecting phase. So it
[152:39] will take some time until the domain is
[152:41] connected. You can check the status of
[152:43] SSL from here. If you just type SSL
[152:46] right here. So, we have a lot of options
[152:49] uh from the hosting panel. What I'm
[152:51] going to do is change the PHP version
[152:53] first from this advanced PHP
[152:54] configuration. I'm going to switch into
[152:57] PHP 8.4. Click on update. Next, I'm
[153:00] going to click on PHP extensions. And if
[153:02] you want to enable or disable certain
[153:04] extensions, you can do this from here.
[153:07] But from PHP options, we have ability to
[153:09] remove some functions from disable
[153:12] functions to increase memory, max
[153:14] execution time and so on. I'm going to
[153:17] remove exec from here because exec
[153:19] function is necessary for this open
[153:21] source package to execute certain
[153:23] commands. I'm going to click on save.
[153:26] Next, if we want to use MySQL database,
[153:28] we can search for MySQL here. We can
[153:30] create a MySQL database and configure
[153:32] our project. But I'm going to stick with
[153:34] SQite for now. And I'm going to open
[153:37] SSH. We have to enable SSH access. By
[153:40] default, it is disabled. So we're going
[153:42] to click on enable here and it's going
[153:44] to be enabled.
[153:46] Okay. After this, we we need to connect
[153:48] to the server using SSH. So we can do
[153:50] this manually or we can skip connecting
[153:52] manually and our open source package
[153:54] will be able to connect it. But for
[153:56] this, we have to generate public private
[153:58] keys. If you don't know what is public
[153:59] private keys, I recommend to download
[154:01] Putty. Then open putty genen in your
[154:05] computer and with just your mouse
[154:07] movement you will be able to generate
[154:09] public private keys. I have more
[154:11] detailed video about the deployment in
[154:14] two ways with the manual work and with
[154:16] the automated package as well. You can
[154:17] check this video in the description or
[154:19] in the top right corner of the screen.
[154:22] So in this case I'm I have existing key.
[154:26] So this is what I'm going to do. I'm
[154:27] going to open puty genen. This is puty
[154:30] genen and you can click on generate to
[154:31] generate new pier or you can load an
[154:34] existing one. I have already created the
[154:36] one which is saved under my home
[154:38] directory. SSH folder. So I'm going to
[154:40] click and open this and this is my
[154:42] public key. So I'm going to copy this
[154:44] entire public key and right here under
[154:47] SSH keys click on add SSH key. Okay, my
[154:51] laptop. Now I'm going to click on add
[154:54] record.
[154:56] So finally we have to take this IP
[154:58] address and put in ENV file of our
[155:01] project which is Hostinger SSH host.
[155:04] This is it. Now we're going to grab the
[155:06] username and put this here. We're going
[155:10] to grab the IP. Put this here. As for
[155:13] the test folder, the uh not the test
[155:16] folder but the as for the hosting your
[155:18] site directory, this is going to be the
[155:20] domain name what we gave to our website.
[155:23] laravelesting.io.
[155:27] Okay. And the final part is to generate
[155:29] GitHub API key. I'm going to go into my
[155:31] GitHub account. Click on the profile
[155:34] settings.
[155:36] Then scroll down below developer
[155:38] settings personal access tokens fine
[155:41] grain tokens. And I'm going to click
[155:43] this generate new token. I'm going to
[155:45] use two-actor authentication from GitHub
[155:47] mobile. Let's give it proper name
[155:49] Laravel medium clone. I'm going to give
[155:52] the project name to it and this is the
[155:54] owner and I'm going to generate this
[155:56] only for the selected repository and
[155:59] this is going to be medium clone laravel
[156:02] medium clone okay perfect and we have to
[156:04] assign two permissions one is the
[156:06] administration and second is going to be
[156:08] secrets administration is necessary to
[156:11] generate deployment key inside our
[156:14] repository which is uh this laral medium
[156:17] clone repository and we need read and
[156:19] write permission and secrets is
[156:21] necessary to generate deployment secret
[156:23] keys in the repository and we also need
[156:26] read and write there. Okay, generate.
[156:29] Now we're going to copy this deployment
[156:31] key and put in our project. Okay, so our
[156:35] like the package setup is ready and then
[156:39] if we have a look in the package
[156:40] documentation, we have a single command
[156:42] to do everything. PHP artisan hosting
[156:45] your deploy and setup CI/CD or we have
[156:48] separate commands for deployment
[156:50] separate for to publish the workflow and
[156:53] uh separate to set up CI/CD. So I'm
[156:55] going to just execute a single command
[156:58] from here and let's wait. Now it is
[157:02] trying to connect to the server. Okay.
[157:05] So in my case it asks me the password
[157:06] and that's because I have added in
[157:09] hostinger my Windows um Windows public
[157:13] key. So I'm actually on Windows right
[157:16] now but I also use WSL with Ubuntu and I
[157:19] have my project inside WSL Ubuntu and
[157:22] this is the reason. So I have not edited
[157:24] my Ubuntu's public key right there. I
[157:27] have public private keys in both Windows
[157:29] and Linux. If I want to add my Linux
[157:32] public key there as well, I'm going to
[157:34] read this uh ID RSA pub from my home
[157:38] directory. Copy this and I can add
[157:42] second SSH key as well. Windows WSL
[157:48] to Ubuntu. Okay,
[157:51] great. Add record. Now let's clear this
[157:54] up and execute PHP Artisan Hostinger
[157:56] deploy SSH connection successfully. It
[157:59] doesn't ask me the password anymore. Now
[158:01] it's going to start all these processes.
[158:04] It's going to set up SSH keys on the
[158:06] server. It is going to connect to our
[158:08] repository and add deploy key there.
[158:11] First, it's detected that we have
[158:13] something in the following folder.
[158:14] Laravel testing io. And if we open this
[158:18] right now, we're going to see that this
[158:20] is the hostinger's default content. Now,
[158:23] there is a single PHP file which renders
[158:26] the following content. So we have two
[158:29] options replace or cancel. I'm going to
[158:31] replace this.
[158:33] Now it is going to perform the
[158:35] deployment of the application. Now the
[158:38] application has been deployed and we can
[158:40] already open this in the browser and
[158:42] we're going to see that our application
[158:43] is there. It's working. We can create an
[158:46] account. We can login. We can create the
[158:48] posts. Everything is going to work. And
[158:50] it also set up right now automatic
[158:52] deployment. and it published the
[158:54] following GitHub workflows hostinger
[158:57] deploy file. So as I mentioned the
[158:59] application is deployed we don't need to
[159:00] do anything but we need to set up
[159:02] automatic deployment and use our uh like
[159:06] past CI/CD workflows there. Now if we
[159:10] have a look inside GitHub workflows
[159:12] hostinger-deploy
[159:13] yml file this is by default what's
[159:16] happening here. So it listens to the
[159:18] push on the main branch because right
[159:19] now on the main branch and we have three
[159:22] jobs and right now inside the deploy it
[159:25] um it's going to perform the deploy even
[159:27] if it is failure. So we're going to
[159:29] remove this failure part
[159:32] only deploy will only happen if the
[159:34] success is completed. Now inside the
[159:36] test it is setting up the PHP executing
[159:40] composer install generating keys and
[159:42] right here on the following place it is
[159:44] running tests. Now let's go into test
[159:47] documentation
[159:49] into continuous integration which you
[159:51] can find in the sidebar and we're going
[159:53] to take some of the things like we want
[159:55] browser testing and we have to execute
[159:58] tests with the following comments. So if
[160:00] we scroll down below we have this
[160:01] browser testing section as well. So I'm
[160:03] going to copy these three steps and I'm
[160:07] going to put this before running tests.
[160:11] Okay. Okay, so it is setting up the
[160:12] Node.js, executing mpm install,
[160:14] installing playright. After this,
[160:19] we have to execute the following comment
[160:24] vendor bean past-ci-
[160:26] parallel run. We can call this past
[160:30] slash browser test. And I'm going to
[160:32] remove this continue and on error. And
[160:35] also browser test needs uh mpm uh mpm
[160:40] assets to be built. So we are going to
[160:42] write here build assets.
[160:46] mpm run build.
[160:50] Okay. So if everything is done correctly
[160:52] then we have to commit and push this
[160:54] file.
[160:56] Once we push this and open repository
[160:58] into actions tab, we're going to see
[161:01] that these jobs are running, running
[161:04] tests, building assets. And let's wait
[161:06] for some time. And just like this, the
[161:09] deployment has been completed. All three
[161:11] steps are there. And if we refresh our
[161:14] website, we are not going to see any
[161:15] changes here. Um, but the deployment
[161:18] worked and the tests we are run. So we
[161:21] can check this run tests section and see
[161:24] what happens. build assets run past
[161:26] browser tests. Expand this and look at
[161:29] this test will run in parallel. All 39
[161:32] tests were passed. All right, my
[161:34] friends. That's it for now and I hope
[161:36] you learned something new. Please leave
[161:38] a like and subscribe. Also, you might
[161:40] want to check out the following course
[161:41] where I'm going to show you how you can
[161:43] build REST APIs using Laravel and deploy
[161:45] them on production environment. That API
[161:48] uses AI features as well. Thanks for
[161:50] watching and I will see you in the next
