What You'll Learn in This Testing Course
45sQuick overview of the entire course content hooks viewers interested in Laravel testing.
▶ Play ClipThis 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.
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.
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.
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 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.
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).
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.
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.
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.
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.
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`.
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`.
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 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()`.
Assertions include: `assertTitle()`, `assertChecked()`, `assertSelected()`, `assertAttribute()`, `assertEnabled()`, `assertUrlIs()`, `assertElementsCount()`, `assertScreenshotMatches()`. These allow comprehensive UI testing.
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.
Use `assertNoConsoleLogs()` and `assertNoJavaScriptErrors()` to verify that pages have no console errors. This is useful for catching JavaScript issues during testing.
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()`.
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.
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 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/`.
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.
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()`.
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.
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.
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.
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.
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.
"Title accurately describes the content: a comprehensive crash course on Laravel testing with Pest v4."
What are the four types of automated tests mentioned?
Unit tests, feature tests, integration tests, and browser tests.
04:39
What is the difference between Pest and PHPUnit?
PHPUnit is the core testing framework; Pest is built on top of PHPUnit and provides a more elegant syntax with expectations API.
09:10
What command creates a unit test in Laravel?
php artisan make:test TestName --unit
13:15
What is the default database connection for tests in Laravel?
SQLite in-memory database.
19:18
How do you group related tests in Pest?
Using the describe() function: describe('group name', function () { ... }).
39:58
What method is used to emulate mobile viewport in browser testing?
->mobile()
69:53
How do you assert that a page has no JavaScript errors in browser testing?
assertNoJavaScriptErrors()
70:00
What is the purpose of the RefreshDatabase trait?
It resets the database between tests by dropping and reapplying migrations.
76:10
How do you pass data sets to a Pest test?
Using the with() method: test('...', function ($a, $b) { ... })->with([[1,2], [3,4]]).
86:00
What command updates snapshot tests?
php artisan test --update-snapshots
95:01
How do you run only tests with a specific group?
php artisan test --group=groupname
98:10
What method is used to assert JSON structure in API testing?
assertJsonStructure()
103:12
What is the execution order of hooks?
Global beforeEach → local beforeEach → test → local afterEach → global afterEach.
83:03
What is the difference between unit tests and feature tests?
Unit tests test isolated logic without HTTP or database; feature tests test user-facing workflows including HTTP requests and database.
04:39
How do you assert that a post was created in the database?
assertDatabaseHas('posts', ['title' => 'Test Post Title'])
127:10
Definition of Software Testing
Provides a clear, foundational definition of software testing as a systematic verification process.
02:54Four Types of Automated Tests
Clearly distinguishes unit, feature, integration, and browser tests with their purposes.
04:39Characteristics of a Good Test
Lists key qualities: clear, focused, deterministic, fast, and meaningful.
07:08Pest vs PHPUnit
Explains the relationship and advantages of Pest over PHPUnit.
09:10Expectations API Examples
Demonstrates the readable and chainable syntax of Pest expectations.
24:24Browser Testing for Client-Side Apps
Highlights the necessity of browser testing for Inertia/React applications.
55:28Visual Regression Testing
Introduces screenshot comparison for UI testing, a powerful technique.
95:01CI/CD with Test Gate
Shows practical deployment pipeline where tests must pass before deployment.
150:22[00:00] Hello everyone and welcome to my Laravel
[00:02] testing course. In this tutorial, you're
[00:04] going to learn how to write fast,
[00:06] reliable and maintainable tests for your
[00:09] Laravel applications using pest
[00:11] framework. This course is fully
[00:12] hands-on. We will start with several
[00:14] slides and understand what is testing,
[00:16] why it is necessary or what type of
[00:19] tests there exist. After this, we're
[00:21] going to create larl project and write
[00:23] our first tests. In this tutorial,
[00:25] you're going to learn how to write unit
[00:27] tests, feature tests, as well as browser
[00:29] tests, how to test authentication part,
[00:32] databases, models, what are data sets,
[00:35] and how to configure hooks. We're going
[00:36] to get familiar with test configuration.
[00:39] We're going to also learn how to write
[00:41] snapshot and visual regression tests
[00:44] where we take the screenshots and
[00:46] compare it to the previous stable
[00:48] version. We're going to have a look at
[00:49] the device emulation as well and we're
[00:52] going to write tests for APIs as well.
[00:54] We're going to learn how to filter,
[00:55] group, and structure our tests. In a
[00:58] nutshell, we will learn all the
[00:59] fundamental things you need to know to
[01:02] get started with testing. And finally,
[01:04] we will take a real project and create
[01:07] real tests for this project. And at the
[01:10] end of this tutorial, we're going to use
[01:11] continuous integration and deploy our
[01:14] project on a production ready
[01:16] environment where whenever you push
[01:18] something, our written tests are
[01:21] executed and the deployment will only
[01:23] happen if all tests are passed. All code
[01:27] written in this tutorial is open source
[01:29] and the necessary links will be
[01:30] available in the video description.
[01:32] Before we start, I want to ask you to
[01:34] hit the like and subscribe because that
[01:35] really helps me to keep creating such
[01:38] type of content. At the end, as I
[01:39] mentioned, we're going to deploy this
[01:40] project on production environment,
[01:42] assign custom domain to it, and set up
[01:44] automatic deployment where tests are
[01:46] running. As a choice of the hosting
[01:47] provider, we're going to use Hostinger's
[01:49] shared hosting, which is also sponsoring
[01:52] this video. In my opinion, Hostinger's
[01:54] shared hosting is the best and the most
[01:56] affordable hosting option out there. Go
[01:59] to the website
[01:59] hostinger.com/thecodeolic.
[02:01] They always have some sort of discount
[02:04] which gives you ability to get the
[02:06] hosting as low as $3 per month which is
[02:10] ridiculously low compared to what value
[02:12] you're going to get with that price.
[02:14] Each hosting option gives you ability to
[02:16] create more than one website. You have
[02:18] domain for one year and you have bunch
[02:20] of other advantages. Choose whichever
[02:22] option is the best for you. Then you can
[02:25] adjust your business web hosting period.
[02:27] The most reasonable is probably one
[02:29] year, but the biggest discount you're
[02:31] going to get if you choose biggest
[02:33] period. If you decide to get the
[02:34] hosting, use a coupon code the code
[02:36] holik and you will get extra 10% off.
[02:39] Now, let's focus on the course and at
[02:41] the end we're going to use this hosting
[02:42] to deploy our project on custom domain
[02:44] on production environment and we're
[02:46] going to set up automatic deployment as
[02:48] well where tests are running before they
[02:49] are deployed to the server. First, let's
[02:51] understand what is software testing.
[02:54] Software testing is a systematic process
[02:56] of verifying that your application works
[02:58] correctly and behaves the way you expect
[03:01] it to behave. Testing ensures that
[03:04] features continue to work as intended
[03:08] even after months of development, major
[03:10] refactoring or team changes. Here are
[03:13] three main reasons why testing is
[03:16] necessary. To verify your code performs
[03:19] exactly as designed under various
[03:21] conditions and edge cases. To catch bugs
[03:25] during development before they even
[03:27] reach to your users and to make changes
[03:32] confidently knowing tests will alert you
[03:36] to any unintended consequences. Let's
[03:39] make this specific to Laravel
[03:41] developers. In Laravel, when you have
[03:43] tests written for your project and you
[03:45] make some changes, it automatically can
[03:47] detect that new code breaks some
[03:49] existing functionality and eliminate
[03:52] that and report this to you. It can save
[03:55] your development time knowing that you
[03:58] have something which already tests your
[04:00] application. You don't need to test it.
[04:02] It runs the entire test suite instantly
[04:06] instead of manually testing features
[04:07] repeatedly. It gives you ability to
[04:10] safely refactor the code of your
[04:12] project. Knowing if something is broken,
[04:15] test will catch it. It gives you ability
[04:18] to build and deploy without a fear.
[04:20] Knowing your comprehensive test coverage
[04:23] has your back. And finally, everything
[04:25] comes down to deliver your project in
[04:28] high quality to ship features that works
[04:31] in various cases and that keeps working
[04:34] over time when new features are added.
[04:36] There are four types of automated tests.
[04:39] Unit tests which has the main purpose to
[04:42] test isolated logic of your project.
[04:45] Typically, unit tests test a specific
[04:48] method or specific class. It is fast and
[04:51] focused on no database, no HTTP
[04:54] dependencies, just plain logic. We have
[04:57] feature tests which test the entire
[04:59] userfacing workflows including roads,
[05:01] controllers middlewares models
[05:03] database interactions and views. These
[05:07] simulate real application behavior. We
[05:09] have also integration tests to verify
[05:12] how multiple systems work together, how
[05:15] they interact, how they share data. And
[05:17] we have browser tests which simulate how
[05:21] actual user behaves in your actual real
[05:25] browser such as clicking on the buttons,
[05:28] typing form submissions and complex UI
[05:31] workflows. Pest for which we're going to
[05:34] cover in this course makes this
[05:36] remarkably simple. Now let me give you a
[05:38] couple of differences between manual
[05:40] testing and automated testing. Manual
[05:42] testing is timeintensive. It requires
[05:45] dedicated human to test every feature
[05:48] when it's developed. Plus, you're going
[05:50] to retest all previous features. If you
[05:54] make some changes in the code that might
[05:56] be connected to the pre previous
[05:57] feature, you get inconsistent results in
[06:00] case of manual testing. Manual testing
[06:03] is also errorprone. It's easy to miss
[06:05] subs or forget to test specific scenario
[06:08] under time pressure and you have to do
[06:11] repetitive work. Same manual steps must
[06:15] be performed again and again and again
[06:18] for every code change. In case of
[06:20] automated testing, it is lightning fast.
[06:23] Execute hundreds of thousands of tests
[06:26] in seconds with a single comment. You
[06:28] are perfectly consistent. Tests run
[06:32] identically every time, following exact
[06:35] same steps without variation. You have
[06:38] comprehensive coverage, meaning never
[06:40] forget steps, never skip checks,
[06:42] automated tests, always verify
[06:44] everything and continuous validation to
[06:48] run your tests on every comet, pull
[06:51] request or deployment workflows inside
[06:54] CI/CD pipelines that changes your
[06:56] deployment way. Once you have written
[06:58] those automated tests, they continue
[07:00] working for you throughout the entire
[07:03] project lifetime and reducing time and
[07:06] effort for you in this project. Now,
[07:08] what makes a test good test? When it is
[07:11] clear and easily readable. When it is
[07:14] focused on one thing, not multiple
[07:17] things. When it is deterministic. Tests
[07:20] must produce every time they execute
[07:24] them the same result. No randomness, no
[07:27] dependencies on external APIs or current
[07:29] date, time or some other information.
[07:33] Always produce the same result in the
[07:35] same circumstances. Tests must be fast
[07:38] and tests should be meaningful as well.
[07:40] If you have a look just the test cases
[07:42] what you have written for your
[07:43] application, you should have good
[07:45] understanding what your project does.
[07:47] Laravel provides an exceptional out
[07:49] ofthe-box solution for testing, making
[07:51] it one of the most developer friendly
[07:54] PHP frameworks for building robust test
[07:58] suites. Everything you need is included
[08:01] and thoughtfully designed to make
[08:03] testing feel natural and intuitive. It
[08:07] has well organized test structure inside
[08:10] test folder. You have dedicated test
[08:11] environment if you want. So you can
[08:13] createin testing and just configure the
[08:16] database or some other parameters only
[08:19] for testing. You can have automated
[08:21] database management as well and laral
[08:23] provides really helpful testing
[08:26] utilities for HTTP requests such as get
[08:28] post to act as an authenticated user to
[08:32] validate to interact with JSON APIs and
[08:35] so on. Now I think it's time to open the
[08:37] code editor and create our first test.
[08:40] Now let's create new Larl project and
[08:43] have a look at testing. First I'm going
[08:45] to execute Laravel new Laravel testing
[08:48] course. I'm going to choose React
[08:50] Starter Kit because it really doesn't
[08:52] matter but I'm going to make this a more
[08:54] advanced type of application where we
[08:55] have this single page application with
[08:57] React and Laravel. So I'm going to
[08:59] choose React. I'm going to stick with
[09:01] Laravel's built-in authentication and
[09:03] I'm going to choose Pest as the testing
[09:06] framework. Let me give you a quick
[09:08] explanation. What's the difference
[09:10] between pest and phpunit? PHPUnit is old
[09:14] and it is the core of the testing. Pest
[09:18] is built on top of phpunit and it gives
[09:21] more elegant and nicer syntax to work
[09:25] with testing and pest 4 has additional
[09:27] great features which makes it super
[09:29] super nice to work with. So we're going
[09:31] to choose past. I'm going to choose no
[09:33] here and let's wait until the project is
[09:35] created. I open the project using
[09:36] phptorm. Then we're going to bring up
[09:39] terminal and we're going to execute PHP
[09:41] artisan test. This is the one way to
[09:44] execute tests. And as you can see all 41
[09:47] tests are passed or we can also execute
[09:51] vendor bean pest. These tests are
[09:54] located under test folder and let's have
[09:58] a look quick look. So we have test PHP
[10:01] which is the configuration file for
[10:03] tests and we have then unit tests inside
[10:07] the unit folder and at the moment we
[10:10] don't have unit tests actually we have
[10:11] only one example test and we have
[10:14] feature tests. All the tests what have
[10:16] been uh run right now are feature tests
[10:20] at the moment. So we have this example
[10:22] test and then we have the dashboard test
[10:24] and some tests related to settings and
[10:27] out as well. And each file also contains
[10:31] several tests inside. All right. So this
[10:34] is the structure. We have test folder.
[10:36] We have test PHP and we have feature and
[10:39] unit. And inside we're going to create
[10:41] all the tests. I'm not going to go these
[10:44] tests and just explore them. Instead,
[10:47] I'm going to simply delete all files
[10:49] from unit
[10:53] as well as from feature because we're
[10:55] going to create them manually. Now,
[10:57] let's bring up terminal. And if we
[10:59] execute vendor being test right now, we
[11:02] see no tests found or I'm going to stick
[11:04] with PHP artisan test. But before I
[11:07] execute this, I'm going to type PHP
[11:08] artisan test list, which is going to
[11:11] give us actually column column list.
[11:15] There are no comments defined in the
[11:17] test name space. So there's nothing.
[11:19] However, if we execute PHP artisan make
[11:23] colon test dash help, then we are going
[11:28] to see several flags associated to this.
[11:31] So if we want to create new test file we
[11:34] have to execute make test then provide
[11:36] the test name and additionally we have
[11:39] options to create a unit test to create
[11:42] a past test which is the default one to
[11:46] create PHP unit test and we're going to
[11:50] talk about the differences between the
[11:51] past and PHP unit test syntax right now.
[11:55] So I'm going to show you both examples
[11:57] and we have a couple of options. First
[12:00] let's start with test and I'm going to
[12:02] create new uh test. Let's call this test
[12:07] or let's call this sample test.
[12:11] Okay, sample test was created. We can
[12:14] open this inside test folder feature by
[12:17] default it created feature test and if
[12:20] we click on this this is the feature
[12:22] test. We are testing certain thing and
[12:25] we are going to give it a proper
[12:27] description. for example
[12:30] should open homepage and it works.
[12:36] So in this case we're accessing the
[12:38] homepage of the application. We're
[12:40] getting the response and we're checking
[12:43] if that response has a status code 200.
[12:47] It means our application is good and it
[12:50] is working. All right. So we can bring a
[12:52] terminal and execute PHP artisan test or
[12:57] vendor being passed. Let me actually
[12:59] execute PHP artisan test. Now we're
[13:03] going to see that one simple test has
[13:05] been passed. It should open homepage and
[13:08] it works.
[13:10] Now let's create unit test. PHP artisan
[13:15] make test sample test but let's call
[13:17] this d- let's give it flag d-unit.
[13:21] We're going to hit the enter. Now we're
[13:22] going to see that sample test is created
[13:24] inside tests unit folder. Let's open and
[13:28] have a look. Now this is the unit test.
[13:30] The purpose of the unit test is to test
[13:32] certain functionality. In most cases, it
[13:35] should test some heavy logic, some
[13:38] method or classes in your application.
[13:41] It is not integration or feature test
[13:43] meaning it is not making requests into
[13:46] the application. So this is a very
[13:48] simple test. It expects that certain
[13:51] value to be true. All right. Now let's
[13:54] bring terminal and execute PHP artisan
[13:56] test again. Now we're going to see both
[13:59] tests are passing. This is the unit
[14:01] test. This is the feature test. Now this
[14:05] in both cases we have saw we we have
[14:07] seen the unit test as well as the
[14:10] feature test but the syntax is pest. So
[14:14] we are using pest. But as I mentioned,
[14:16] pest is being built on on top of PHP
[14:19] unit. So if you open composer JSON right
[14:22] now, we're going to see that we have
[14:24] pest here. We have past php and pest
[14:27] plug-in level. Pest by the way is a
[14:30] standalone package which is for testing
[14:34] PHP applications but it's um is
[14:37] developed by Nun Maduro which is the
[14:39] core member of Laravel team and he also
[14:43] developed this pest plugin Laravel and
[14:45] pest works great with Laravel but the
[14:48] thing is that if we open right now
[14:51] composer lock file and if we search for
[14:54] pestph php / test
[14:59] we're going to find that here it is. So
[15:02] this is the package installed and inside
[15:05] its dependencies there's this PHP unit.
[15:07] So internally it is still using PHP unit
[15:11] just it is a wrapper with a nicer syntax
[15:13] more intuitive in my opinion as well and
[15:16] you write much less code as well. Now
[15:18] let's bring up terminal and execute PHP
[15:21] artisan make test. Let's call this make
[15:24] test two uh two here and we're going to
[15:28] provide a flag by the by default we know
[15:30] that this is going to create past
[15:32] feature test right now let's provide
[15:36] flag called d-phpunit
[15:39] now this is going to create this class
[15:40] in a php unitit syntax if we open right
[15:45] now this inside test folder feature
[15:49] sample test 2 we're going to see that
[15:53] This is a class. This is a typical
[15:54] objectoriented way of testing your
[15:57] applications. It is a class. It extends
[15:59] this test case. Uh it u has this test
[16:04] example. That example is the same text
[16:08] you would give to your uh feature test
[16:11] right here inside this test. So in our
[16:14] case we should call this
[16:18] it should underscore
[16:22] open home age and it works. So this is
[16:28] how we would name the method. Now let's
[16:32] open terminal and execute PHP artisan
[16:35] test and have a look.
[16:38] Now all tests are running. Where is this
[16:41] simple test two? Actually, it should end
[16:44] with a test. That's why it wasn't
[16:46] executed. We're going to call this
[16:48] sample two test.
[16:50] Sample two test. And we have to we have
[16:53] to rename it. Apologies. Sample two
[16:58] test. Okay, let's rename this. Now, it
[17:02] was renamed as a file as well.
[17:05] Now, let's execute PHP artisan test. And
[17:08] we're going to see three tests passed.
[17:10] And we see the these two tests are kind
[17:13] of identical. So this is coming from
[17:15] this sample two test and this is coming
[17:17] from the sample test. This is the PHP
[17:19] unit syntax. This is paste syntax. And
[17:23] this is a typical uh unit test. Now
[17:26] let's create new test in PHP unit syntax
[17:30] which is going to be unit test. We're
[17:33] going to execute PHP artisan make test.
[17:35] Let's call this sample two test. Again
[17:38] we want PHP unit syntax and we want unit
[17:42] test not feature test anymore. Now it
[17:45] created this file inside unit folder.
[17:47] Again let's go inside unit folder. Open
[17:50] this. And it looks very similar to what
[17:53] we have already seen right here. Just
[17:57] the assertion way how we check some
[18:01] values is different. So in this case we
[18:04] are using expectations API. we expect
[18:07] something to be something but in this
[18:09] case we are using in PHP unit we are
[18:12] using assertions API we assert this
[18:16] value to be true so uh PHP unit this one
[18:21] only supports assertions API it doesn't
[18:23] support expectations API but
[18:28] past actually supports both so we can
[18:30] take this code this assert true equals
[18:33] true and we can put this here we can
[18:35] comment this part and we can execute
[18:38] tests again and we're going to see that
[18:40] it works as well. So two unit tests are
[18:42] executed, two feature tests are
[18:44] executed. So that's why I personally
[18:46] like writing test syntax because this is
[18:49] the same as this and we don't need to
[18:52] create the class and everything and also
[18:54] naming is like very very inconvenient in
[18:59] your PHPUnit way. So in this case we can
[19:01] just write
[19:03] it checks that the value is
[19:09] is true. Right? So we save this execute
[19:11] this but in this case we have to give it
[19:13] a name with underscores. Let's talk a
[19:15] little bit about configuration of our
[19:18] tests or how we can change the database
[19:21] or some environment variables
[19:23] specifically for tests. All tests by
[19:26] default are using PHP unit.xml XML file
[19:30] if the test needs the database. So here
[19:33] we have these environment variables the
[19:37] ENV is testing we have this maintenance
[19:39] driver to be file and all the other
[19:41] things and the DB connection is SQLite
[19:44] and the database is in a memory
[19:46] database. So whenever we execute the
[19:48] test it is going to do all the things
[19:50] inside the memory create migrations and
[19:52] everything and then uh the memory is
[19:54] gone basically once the test is
[19:56] finished. If we comment out this part
[20:00] and execute our tests and our tests are
[20:02] using database those tests will be
[20:05] executed on our primary database what we
[20:08] have written inside file.
[20:12] If we don't want that we also have
[20:14] ability to create env.esting
[20:18] file exclusively for the environment
[20:21] variables that is specific for testing.
[20:24] So in this case we can delete everything
[20:26] from here let's say and we can only
[20:28] leave DB connection related information.
[20:32] We can specify the database connection
[20:34] to be SQLite uh and database
[20:38] DB database to be in memory as well. So
[20:41] we also have that flexibility or if you
[20:44] want to override a specific environment
[20:47] variable from the default in you can do
[20:50] this from here as well. Let's say you
[20:52] want to change the mailer to be log
[20:55] instead of some SMTP. So all that is
[20:58] possible through this env.esting
[21:00] file from this phpunit.xml
[21:04] file. We also have ability to customize
[21:07] the directories for unit tests as well
[21:09] as for feature test. As you can see by
[21:12] default we have the unit tests are
[21:14] created inside test unit and feature
[21:17] tests are inside tests feature. So you
[21:19] can customize this if you want.
[21:22] Now let's close these two files and
[21:26] let's go inside. Actually um I'm going
[21:29] to close this. I'm going to delete this
[21:30] yen testing as well because I prefer it
[21:32] to be using uh the settings what I have
[21:35] defined right here and I'm going to
[21:36] uncomment this part as well. Okay. Now
[21:39] let's go inside test folder and open
[21:42] pest.php.
[21:44] So this is the main configuration file
[21:47] for pest. From here we can decide in
[21:50] which browser by default the test should
[21:52] be run, what configuration options it
[21:55] should have by default and so on. So if
[21:58] you have a look right here, all tests
[21:59] are extending tests test case and also
[22:04] it uses refresh database class every
[22:08] time which means that every time we
[22:10] execute the test it is going to just uh
[22:14] refresh the entire database drop create
[22:16] migrations and then it is going to run
[22:19] them inside features. Okay, all tests
[22:22] are basically all feature tests will be
[22:24] inside feature target so-called this as
[22:27] an example here we have extended
[22:30] expectations and added a new
[22:32] expectations method to be one. So using
[22:36] this way we have ability to define
[22:38] custom expectations here as an example
[22:42] let's write I'm going to actually paste
[22:44] this I think it's going to be easier and
[22:46] I will explain this. So to be uid as an
[22:50] example so we are defining new
[22:52] expectations method where this value
[22:56] needs to be needs to match the UID and
[23:00] that's it. So whatever is returned from
[23:02] here true or false it's going to be
[23:05] tested and we can use this to be UUID
[23:08] inside our tests. Additionally I'm going
[23:11] to do this right here. We have ability
[23:13] to customize certain things globally
[23:16] such such as I'm going to call test
[23:20] browser
[23:22] and let's say that we want to run this
[23:24] um always in Firefox. So we can do this
[23:27] in the following way. Of course, we have
[23:29] other browsers such as in Safari, in
[23:32] Chrome and so on. We also have ability
[23:35] to customize the global timeout. By
[23:38] default, test is waiting for several
[23:41] seconds, I think 5 seconds until the
[23:44] element gets visible in the browser to
[23:47] decide if the test is failed or not. And
[23:50] we have flexibility for this. We can
[23:53] provide different number here. Let's say
[23:54] 10,000 milliseconds, 10 seconds. And
[23:58] based on that now pest is going to wait
[24:00] a little bit more. We also have ability
[24:03] to provide a global user agent to all
[24:06] tests what we execute right here. And I
[24:10] think that might be helpful in certain
[24:12] cases. Now I'm going to delete all
[24:14] feature tests and sample to unit test as
[24:17] well. And I'm going to leave only this
[24:19] sample test and we're going to
[24:22] understand how we're going to work with
[24:24] expectations API and what expectations
[24:27] are available. As I mentioned, test
[24:31] replaces the assertions API with
[24:34] expectations. Uh it doesn't actually
[24:37] replace assertions is still there. it
[24:40] still works but expectations API is more
[24:44] um more flexible and more readable in my
[24:47] opinion as well. I'm going to remove
[24:49] this assert true okay expect something
[24:52] that something can be an object can be
[24:54] number can be string it can be anything
[24:56] so let's specify expect five as an
[24:59] example and then it gives us the object
[25:01] which has a lot of a lot of functions
[25:05] like expect five to be something as an
[25:09] example to be five so we expect some
[25:11] number and that number might be received
[25:13] from database from some APIs let's say
[25:17] that is a number which um our math class
[25:21] gave us and we expect that number to be
[25:24] 3.14. Let's say this is a pi, right? So
[25:27] pi equals 3.14 and then we expect that
[25:32] pi which is by the way received from
[25:35] some math class to be 3.14. So once we
[25:39] execute this, we're going to see that
[25:40] test is passing. If for some reason that
[25:44] function or class or whatever is
[25:47] returning result from a function
[25:51] or from a class. Okay. So if for some
[25:55] reason that class doesn't work properly
[25:57] and returns something else then our test
[26:00] will fail. Right? So this to be um
[26:04] something is just one example but we
[26:06] have a lot of a lot of expectations and
[26:10] I'm not going to go through all of them
[26:12] but we have some of the most common ones
[26:15] such as to be string as an example. So
[26:18] we expect as an example here zura to be
[26:22] string we can specify that zura must be
[26:24] string it must contain and we can chain
[26:27] also to contain uh z inside there. So
[26:31] let's remove this pi from here.
[26:35] Now if we execute the following test,
[26:36] we're going to see that it is passing
[26:39] and we have two assertions as well. One
[26:41] assertion is that it needs to be string
[26:44] and second assertion is that it should
[26:46] contain Z inside. I'm going to open test
[26:50] expectations
[26:51] um documentation page and here we're
[26:53] going to see everything that is
[26:55] possible. All right. So if we scroll
[26:57] down below, we have this 2B to be array
[27:00] to be between and I'm not going to read
[27:02] all of them obviously, but I encourage
[27:04] you to have a look and understand what
[27:07] expectations are available because that
[27:10] expectations define how easily you can
[27:13] write tests. As an example, you might
[27:15] get an array and you expect that array
[27:17] to have exactly three elements inside
[27:18] there to have count three. And we also
[27:21] can negate our um our expectation. As an
[27:26] example, we can expect Zura to be string
[27:31] to contain Z and then we can chain not
[27:35] to contain lowerase Z as an example. We
[27:39] expect it to be uppercase C. So if we
[27:40] execute this now, we're going to see
[27:42] test is passing. And now we have three
[27:44] assertions. We can also chain uh to
[27:48] because we already use not here. We
[27:51] don't need not anymore. So we negated
[27:54] the entire uh operator entire
[27:56] expectations and then to uh to be
[28:00] integer as an example. So we expect that
[28:03] not to contain Z and not to be integer.
[28:06] If we execute this oh actually um it's
[28:09] my bad. So we need this not to contain
[28:13] and not to be integer. Now we see four
[28:16] assertions are passing. We need to use
[28:18] this not before every um every
[28:22] assertion. Now let's see another
[28:24] example. I'm going to use expect and
[28:27] provide array here. Let's say we have
[28:29] three elements inside array one,
[28:32] two and three. And then we expect to be
[28:37] array
[28:39] that's one assertion. Second is that it
[28:42] to have count of three. And I also want
[28:46] to uh check that it should contain
[28:49] element two. Now uh why do I see this
[28:56] multiple expectations can be changed to
[28:58] changed together.
[29:01] That's page for sure thing.
[29:04] Okay. Expect and oh okay. So that's
[29:07] that's an syntactic sugar on top of what
[29:10] I just shown to you. It uh combine these
[29:13] things. Actually I prefer to have them
[29:15] splitted because this is one thing
[29:16] testing strings. This is one thing
[29:18] testing array. All right. So let's bring
[29:20] up terminal. Execute this. Now we see
[29:22] seven assertions. The test is passing.
[29:25] Now let me show you another example. I'm
[29:27] going to use expect and provide one two
[29:29] and three. Then we can again use some
[29:32] expectation method such as to have count
[29:34] three. But that's not the main thing. So
[29:36] then I'm going to call sequence on that.
[29:39] And when I call sequence, we have
[29:41] ability to provide callback functions
[29:43] for each value of this array. So I'm
[29:47] going to use fn the arrow function
[29:50] basically right here. And then the value
[29:52] inside is already an instance of
[29:56] expectations API. So in this case on the
[30:00] value we can directly call to be integer
[30:03] as an example. So if we save this and we
[30:05] execute that we're going to see that the
[30:07] test is passing and we have 11
[30:10] assertions right now. Why do we have 11?
[30:13] Let's count this 1 2 3 4 5 6 7 8 and
[30:19] then this function is executed three
[30:21] times for each element of the array.
[30:24] That's the thing. If I remove one
[30:26] element from this array and re-execute
[30:29] that
[30:30] uh what's wrong? Oh, this this is what's
[30:34] wrong. We expected to have three
[30:35] elements inside but and now we have two
[30:38] elements inside. Actually, let's let's
[30:40] remove this part. Not necessary. Okay,
[30:42] now we have nine assertions. Now, let's
[30:46] return this back into three. And we are
[30:48] going to have now 10 assertions.
[30:51] We had 11 previously because we had this
[30:54] to have count as well. But the thing is
[30:56] that we have ability to provide multiple
[30:59] callback functions here. Um and those
[31:01] callback functions will be iterated. So
[31:04] in this case if we provide three
[31:06] callback functions we can define to be
[31:10] one. The first callback function the
[31:12] value inside the first callback function
[31:14] to be one. uh the value inside the
[31:17] second callback function to be two and
[31:19] the value inside the third callback
[31:21] function to be three. So if we execute
[31:23] this all tests are passing. That's
[31:25] because those callback functions are
[31:27] executed sequentially on these elements.
[31:31] All right, I hope that makes sense.
[31:34] Now let's create a user instance which
[31:38] is going to be new user and we can
[31:40] provide um for example first underscore
[31:44] name to visura okay I'm going to leave
[31:46] only first name we are not actually
[31:48] creating the user in the database but I
[31:51] want to show you using expect providing
[31:55] user and then we can use the method to
[31:58] be instance of in the user class. So in
[32:02] the following way we can replace this
[32:04] qualifier using import. In the following
[32:07] test we can test in the following
[32:08] expectations. We can test if the user is
[32:11] an instance of a specific class. If we
[32:14] execute this we're going to see that the
[32:15] test is passing. We also have a method
[32:19] expect something as an example. Uh let's
[32:22] replace this into an array and provide
[32:24] user here to contain only instances of
[32:29] if we have a list of users providing
[32:32] here then we can provide user class here
[32:37] using the following assertions we can
[32:39] test using the following expectations we
[32:42] can test if the following array only
[32:44] contains instances of the given class.
[32:47] That is al also helpful one. So again, I
[32:50] encourage you to have a look at the
[32:51] expectations API and just read uh them.
[32:55] So this is my homework to you. Just read
[32:58] what expectations are available. Uh and
[33:02] whenever you need to write a real test,
[33:05] unit test or um some assertion, some
[33:07] feature test, then you know which one
[33:09] you can use. Now I'm going to create one
[33:12] feature test as well and let's see a
[33:14] couple of um expectations on the feature
[33:17] side as well.
[33:19] um PHP artisan make test sample test but
[33:24] this case um it's going to be a feature
[33:27] test. Okay. So inside feature let's open
[33:30] this. So we are using assertion here to
[33:34] check if the response has the status of
[33:38] 200. We can chain different assertions
[33:41] here as an example assert cextable. So
[33:45] we expect that when we open the
[33:47] application the status code should be
[33:49] 200 and there should be text allarl. Now
[33:53] let's execute tests. PHP artisan test.
[33:57] And look at this. Um where's this
[33:59] feature test? Okay, it has the example
[34:01] test. We should give it proper
[34:02] descriptive name. Uh but the thing is
[34:04] that it is passing two assertions. Uh 14
[34:07] assertions more specifically. So that's
[34:10] a total number of assertions actually.
[34:13] But we have two assertions here. And we
[34:15] have a bunch of different assertions
[34:17] here which I'm not going to um focus
[34:19] right now. But step by step we are going
[34:22] to get familiar all of them. Not all of
[34:25] them but the most important ones. Now
[34:27] let's write actual unit tests for my
[34:31] service class. But now I'm going to
[34:32] delete again this feature and unit
[34:34] tests. And let's create a new unit test.
[34:37] I'm going to call this PHP artisan make
[34:39] test and let's call this price
[34:41] calculator test. Let's hit the enter.
[34:44] And now let me show this. Actually,
[34:47] sorry. I should have created inside a
[34:50] unit folder because we are um writing
[34:52] unit test right now. I'm going to delete
[34:54] this feature test. Now, if we go inside
[34:56] app services and open this price
[35:00] calculator class, it has several
[35:02] methods. So, this one is apply
[35:05] percentage discount. We pass the price,
[35:08] we pass the percent and then we check if
[35:11] the percent and the price doesn't fit in
[35:14] the uh valid amounts numbers. Then we
[35:17] throw this invalid discount percent um
[35:21] exception otherwise we simply apply the
[35:25] given percent as a discount on the given
[35:27] price. Okay, we have second method apply
[35:31] fixed discount. We give the price and
[35:34] fixed amount and then we apply this
[35:36] amount as a discount on the given price.
[35:38] We also have added tax. We give the
[35:40] price. We give the tax percent and we
[35:43] add the tax percent on the following
[35:46] price. And we have this final price
[35:48] where we give the price. We have this uh
[35:51] discount percent and tax percent as
[35:54] well. And we calculate um first we apply
[35:58] this discount and then we add tax. All
[36:00] right. So we have these four methods and
[36:03] I want to write now tests for all four
[36:07] methods unit tests and remember unit
[36:10] tests should not use HTTP should not use
[36:14] any external services should not use a
[36:17] database we are testing simply pure
[36:20] logic of a class or a method okay so
[36:22] this is the first test and let's give it
[36:25] applies percentage discount now we're
[36:28] going to create an instance of this
[36:30] price calculator. So price calculator
[36:32] equals new apps service price
[36:34] calculator. Okay, perfect. Then we're
[36:37] going to use expect price calculator
[36:39] actually let's call this calculator to
[36:41] make this simpler. Then on that
[36:43] calculator we're going to call apply
[36:45] percentage discount. We provide the
[36:47] price as an example 100 and let's
[36:49] provide the discount of 20%. So once
[36:52] that discount is applied on price then
[36:56] we expect it to be at.
[37:00] All right let's bring up terminal and
[37:03] execute PHP artisan test
[37:07] and look at this applies percentage
[37:09] discount. The test is failing uh because
[37:13] that 80.0
[37:15] is identical to 80. So what we need to
[37:18] do it returns a float number. So we have
[37:22] to provide 80.0 here as well. Now if we
[37:26] execute this now we see that test is
[37:28] passing. So that's one example. Actually
[37:30] we expected integer to be returned but
[37:33] inside the method float was returned.
[37:37] So that's one test. Now let's write a
[37:40] second test. And by the way we have two
[37:42] ways to write the test. one is using
[37:46] this test method. And if we execute
[37:49] this, we see here applies percentage
[37:53] discount. But if we change this into it,
[37:56] that's a syntactic sugar on top of that.
[37:59] So then if we execute this now, we see
[38:01] it applies percentage discount. So
[38:04] that's just syntactic sugar on top of
[38:07] the test method. So if you open this it,
[38:11] we see that it is actually calling test
[38:14] internally. So let's use it here and I'm
[38:18] going to write second test it uh rounds
[38:21] discounted rice provide a function here
[38:24] then again we have to create calculator
[38:27] equals new app services price calculator
[38:31] and let's actually replace this uh into
[38:34] import. So we have this import at the
[38:36] top then create instances. Okay, now we
[38:38] have the calculator. Now we expect on
[38:41] calculator apply percent discount. Let's
[38:45] give the price to be 100 and the percent
[38:48] to be something like 12
[38:51] 345.
[38:53] All right. And then the result we expect
[38:56] it to be
[38:59] um 87.66.
[39:05] So why we expect this actually? So this
[39:07] 12.345%
[39:10] uh discounted on 100 means that we have
[39:15] remaining
[39:17] uh 80
[39:19] um 87
[39:22] uh 655
[39:24] right but because we apply rounding this
[39:27] is what we are testing. If we go in the
[39:29] price calculator, we see that on that
[39:33] method, we have everything rounded with
[39:35] precision two. So instead of 87.655,
[39:41] we expect it to be 87.66
[39:44] just to test that the rounding works
[39:47] successfully. All right. Now if you
[39:49] bring up terminal and execute PHP test,
[39:52] now we see two tests are uh passing. It
[39:55] rounds discounted price. Now let's see
[39:58] how we can groups similar tests into the
[40:01] same group. We're going to take these
[40:04] two tests which is related to applying
[40:07] percentage discount. So I'm going to
[40:10] take this and then I'm going to use the
[40:11] method called describe and I'm going to
[40:14] give it uh like price calculator dash
[40:18] percentage discount. Okay. So then we
[40:21] provide a callback function here and we
[40:23] are writing all our tests inside that
[40:26] callback function. I'm going to paste
[40:28] them. We're going to save and execute
[40:31] them. And now we see that this is the
[40:34] group now. Price calculator percentage
[40:36] discount it applies percentage discount
[40:39] and in the same group we have it rounds
[40:42] discounted price. So we have two tests
[40:45] and now we can go ahead and write um
[40:47] another test which is for the fixed
[40:50] discount. I'm going to introduce another
[40:53] describe here price calculator fixed
[40:55] discount and let's create um several
[40:59] tests first. Let me define those tests.
[41:01] This is also a good practice to define
[41:04] your tests first. So applies fixed
[41:07] discount. So this is the one test we're
[41:09] going to write. Second is going to be
[41:12] eat um it
[41:16] uh never returns
[41:20] negative price.
[41:22] Um
[41:24] I don't want it to be skipped. The
[41:26] autocomp completion is sometimes really
[41:28] good but sometimes it is annoying when I
[41:30] don't want when I want step by step to
[41:32] introduce something.
[41:35] Another test it throws
[41:39] or negative fixed amount we need
[41:43] function here and I think the three
[41:46] tests are kind of enough. So we know
[41:48] that inside this group we're going to
[41:50] have those three uh tests. Hey, can you
[41:54] do me a favor and pause this right now?
[41:57] Hit the like button, subscribe if you
[41:59] are not yet because I'm working on some
[42:01] really interesting videos for you and
[42:03] then continue the course. Thank you.
[42:05] Now, if we bring up terminal and execute
[42:07] this, we're going to see that these
[42:09] tests don't perform any assertions. So,
[42:12] we see this in yellow. This kind of good
[42:14] indicator as well that we don't have
[42:16] those tests implemented, right? So,
[42:19] let's start with the first one. It
[42:21] applies fixed discount. We're going to
[42:23] create calculator here.
[42:26] Then on that calculator, we're going to
[42:28] call apply fixed discount with
[42:31] 120. But we're going to wrap this inside
[42:34] expect.
[42:36] And then on that expect we're going to
[42:38] call the result to be 80.0.
[42:42] So we save this. We execute this. And we
[42:45] see that this test has been passed. We
[42:49] have other two tests where we don't have
[42:51] anything implemented. Right now the next
[42:54] one is to test that it never returns a
[42:57] negative price. Right? So we're going to
[43:00] provide we're going to create the
[43:01] calculator then on expect we are going
[43:04] to call calculator apply fixed discount
[43:08] and in this case I'm going to provide
[43:09] the value to be 50 and the percent to be
[43:13] 100 right so and then we expect the
[43:17] result
[43:19] to be 0.00 0 0. So we don't expect the
[43:22] result to become negative even though we
[43:26] applied uh $100 discount dollar or 100
[43:31] amount discount on the 50 uh price.
[43:35] Right? So let's bring up terminal
[43:37] execute that and the test is actually
[43:40] failing. Oh sorry not expect to be 2B
[43:44] 0.00 0. Okay, let's execute this. And
[43:47] now we see that this test is also
[43:49] passing. And the last one we're going to
[43:52] test that it throws for negative fixed
[43:54] amount. So we create the calculator.
[43:57] Then on that calculator
[43:59] uh we are calling
[44:02] expect
[44:04] calculator apply fixed discount and we
[44:08] provide minus 10 to be the discount
[44:11] price and we expect
[44:15] expect exception. So there are a couple
[44:17] of ways. So we can um we can use the
[44:21] method to throw. So we expect that it to
[44:24] throw invalid argument exception. That's
[44:27] the one way to do this. We're going to
[44:29] execute this and we see the following
[44:32] error. Well, this is the thing. Yeah,
[44:34] that's a good point. So we the test
[44:37] wasn't passed. Uh instead we saw an
[44:40] exception. That's because this method
[44:43] throw an exception and that happened in
[44:45] the main thread where the test was
[44:48] running. So in order to test this, we
[44:51] need this error. We need to wrap this
[44:53] inside the function. Now when we put
[44:56] this inside the function, test will
[44:58] check if it throws an exception and
[45:01] catch it and properly gives us the
[45:04] result. Now we see that uh it throws for
[45:08] negative fixed amount. The test is
[45:10] passing. So it throws the exception. So
[45:12] if it throws the exception, you should
[45:14] not use in the way like this. Instead,
[45:18] we're going to put this inside a
[45:20] function. Now, let's create another
[45:21] group. And I'm going to give this a
[45:23] challenge to you. So, we're going to
[45:26] test um let's create the group first.
[45:28] And we're going to test that it adds tax
[45:32] and final price that it tests the final
[45:36] price as well. Okay. First, we need
[45:39] it
[45:41] adds tax to the price.
[45:48] Good. Second is that it rounds tax
[45:52] price.
[45:54] Rounds
[45:56] taxed price.
[46:00] And the third one is it calculates final
[46:03] price with discount and tax. We can
[46:08] provide additional text as well with
[46:10] discount and tax. Now this is a
[46:14] challenge to you. Please go ahead and
[46:17] implement these three unit tests based
[46:20] on this price calculator class. You can
[46:23] get the price calculator source code
[46:25] from the project um and you can find the
[46:28] project link in the video description
[46:30] and implement these three methods and
[46:33] then come back and see my solution.
[46:35] Okay, I hope you made it. Now let's see
[46:38] how I'm going to do this. First
[46:40] obviously we're going to create the U
[46:42] calculator. Next on that calculator,
[46:46] we're going to call method add tax and
[46:49] we're going to wrap this inside expect.
[46:52] We add 10% tax on top of that or we can
[46:57] replace this into like 21% as well to
[47:00] have that not that round number. And
[47:03] then we expect the price to be 121. This
[47:07] is the first test. In the next test,
[47:10] again we're going to create the
[47:11] calculator. then we expect um to add tax
[47:16] on that calculator. So we take this
[47:18] price to be 100 uh point one two three
[47:24] whatever number here right then we add
[47:27] tax percent to be 21%. In order to
[47:30] decide what value we're going to expect
[47:33] we have to do a little bit of
[47:34] calculation on ourselves as well right
[47:36] so let's bring up terminal and in the
[47:38] calculator we're going to take this 100
[47:43] 100.12345.
[47:45] We're going to multiply this on 21
[47:50] which is a tax percent and we're going
[47:52] to divide this on 100 and this is going
[47:56] to be the tax amount value. Right? Then
[48:00] when we add this 21.025
[48:03] 025
[48:05] whatever is this we're going to plus
[48:10] 100.12
[48:12] 3 4 and five. So finally we get
[48:17] 12149.
[48:21] So if we round this 12149
[48:28] into two uh two um precision then we
[48:33] should expect here to be 15
[48:37] right so let's execute the test it
[48:41] rounds tax price this is correct and the
[48:44] last one is remaining the autocomp
[48:46] completion gave us wrong value here so
[48:49] that's why it's kind of annoying rank
[48:51] sometimes but we calculate it ourselves
[48:54] and we know what we are expecting that
[48:57] the tax price should be rounded properly
[49:00] and the final one is to calculate the
[49:03] final price with discount and tax. Again
[49:07] we're going to create the calculator.
[49:09] Let me do this step by step. uh then let
[49:12] me call expect on the calculator
[49:16] to call final price and let's provide
[49:20] 100 as a final price not the final as an
[49:25] initial price. Then we provide 10 here
[49:28] inside the discount percent and we
[49:31] provide 21 as a tax percent. So we
[49:34] expect the value to be something. So we
[49:38] have to calculate this by ourselves and
[49:40] then we decide what we expect. So if we
[49:44] open again terminal
[49:47] a little bit of calculator. So then we
[49:50] take that 100 and we're going to
[49:53] subtract the discounted price which is
[49:56] going to be 10% and so finally we have
[49:58] 90 remaining. Right? And then we're
[50:01] going to add 21%
[50:04] on 90. Right? So 90 plus 90 ultiplied on
[50:10] 21 divided on 100. So that is uh 108.9.
[50:18] So finally we expect
[50:21] it to be
[50:23] 108.9.
[50:26] Now we save this
[50:29] and execute PHP artisan test. And let's
[50:33] have a look.
[50:35] Oh, we have semicolon missing here. Not
[50:38] a big deal. Okay, one test was failed.
[50:43] Expect doesn't exist in double again.
[50:46] That should be 2B. Not expect but 2B.
[50:50] Okay, now all tests are passing. And
[50:53] congratulations. Right now we have
[50:55] written a real world scenario of unit
[50:59] tests of our service class. And we have
[51:01] written how many tests? We have eight
[51:04] tests written in our project which is
[51:06] pretty cool and um not as complex as you
[51:09] might find in uh real projects uh large
[51:12] projects but this is a good start and
[51:15] good um good unit tests. This is brand
[51:19] new Laravel React starter kit which we
[51:21] have installed and it has the welcome
[51:24] page, it has a login page and a register
[51:27] page and we also have uh on the login
[51:29] page forget password page as well. So
[51:32] now we are going to write feature tests
[51:35] and write test to make sure that the
[51:38] homepage welcome page opens uh the login
[51:41] register pages are opening and like we
[51:45] see the information on those pages right
[51:47] so let's open terminal
[51:50] and I'm going to execute PHP artisan
[51:54] make test and I'm going to call this
[51:57] public pages test however we can put
[52:01] this inside a subfolder by providing
[52:04] something the subfolder name. So we can
[52:07] call this um roads dot or not dot but
[52:11] slashpublic pages test. We hit the
[52:14] enter. Now we see this public pages test
[52:17] is created inside roles folder. So we
[52:21] can open this public pages test and we
[52:24] see an example uh test right here. So
[52:27] let me replace this with it and that
[52:30] should be shows welcome page. By the
[52:34] way, this is one way how we're going to
[52:35] make request this get. But the second
[52:37] way is to simply use get method in which
[52:40] case we have to import the get function
[52:43] as well from past lar. Okay. So we you
[52:47] can choose whichever you want. I'm going
[52:49] to stick with this get here.
[52:52] Okay. So this shows the welcome page and
[52:55] makes the status is correct. Let's bring
[52:56] up the terminal and I'm going to execute
[52:59] PHP artisan test and we're going to see
[53:01] that this feature test is actually
[53:03] passing.
[53:05] Now let's create another test. It
[53:10] uh shows the login page. All right. Now
[53:15] in this case we're going to make request
[53:18] response equals get login on the login
[53:20] endpoint. We can save this into a
[53:23] response variable and then on that
[53:25] response variable we can call some
[53:28] assertions or another way is to change
[53:31] several methods. So we get the response
[53:33] then we can check the status uh needs to
[53:36] be 200. What else we expect on the
[53:38] homepage? Let's open the browser. Click
[53:42] on login and then we expect the login to
[53:46] your account text to be also visible. Um
[53:50] and that's it. Yeah. So then we can use
[53:53] another assertion like assert C login to
[53:57] your account. Optionally we have a lot
[54:01] of assertions here. Assert and assert
[54:04] accepted. Assert accepted um basically
[54:07] checks a specific status code. It's a
[54:10] 202 status code. So we are not we don't
[54:13] need to cover all of them. But let's
[54:15] have a look. Assert those are related to
[54:18] status codes. Assert okay is again the
[54:21] 200 status code
[54:24] unauthorized assert cookie if the cookie
[54:27] contains specific value. Assert C and
[54:30] assert don't see. So we used assert C
[54:33] and we expect the following text to be
[54:35] displayed. Um and we can use assert
[54:37] don't see as well such as um such as
[54:41] welcome to Laravel or dashboard. So we
[54:44] don't expect dashboard to be displayed
[54:47] when we access to the login page. Let's
[54:49] bring up the terminal and execute PHP
[54:51] artisan test and let's have a look.
[54:54] Login to your account. This is not
[54:56] visible. So this is the thing. So this
[54:58] is not browser testing. It is making it
[55:01] is making curl request getting the HTML
[55:05] and then inside that HTML it is
[55:08] searching for the following um for the
[55:11] following content. And I expect this is
[55:15] not going to work as well. And I'm going
[55:19] to explain why.
[55:22] Okay. Yeah, this is not going to work.
[55:24] And the reason is that we have inertia
[55:28] laral inertia type of application. All
[55:30] right. And the content is rendered
[55:33] dynamically. So if we view the page
[55:36] source, this is what um paste right now
[55:41] sees when we make the following request.
[55:44] This is it. This is inertia page and we
[55:47] don't see login here. Log in do we see?
[55:50] No, we don't see. That's the reason. And
[55:53] we are using the login.tsx file which is
[55:57] dynamically rendered in the browser.
[56:00] That's why uh pest for's browser testing
[56:03] is absolutely amazing and using browser
[56:07] testing we will be able to test all this
[56:09] functionality like the login and the
[56:11] text is visible and the buttons are
[56:13] working and the redirects are working
[56:16] and everything. To get started with
[56:18] browser testing, we have to install pest
[56:21] plug-in browser as well as playright
[56:24] because internally pest plug-in um 4
[56:27] version 4 for browser testing is using
[56:30] playright. So we're going to copy the
[56:32] following command bring up terminal
[56:34] paste this and hit the enter. So at this
[56:36] stage, look at this. It is downloading
[56:38] Chromium uh browser and it is going to
[56:41] actually test everything inside the real
[56:44] browser which is uh open-source version
[56:47] of Chrome. In my case, some of the
[56:49] playright dependencies we're missing. So
[56:52] I can execute the following command or I
[56:55] can execute the following commands.
[56:58] Okay, the dependencies have been
[57:00] installed and if we go right now inside
[57:02] this public pages test, we can replace
[57:06] get
[57:08] with visit. So we use visit to have a
[57:12] look um and to open this in the browser.
[57:14] Assert status function doesn't exist
[57:16] after the visit method. So we have to
[57:19] remove this. So we expect login to be
[57:21] displayed and we expect dashboard text
[57:24] not to be displayed as an example. We
[57:26] can replace this with visit as well. So
[57:29] the thing is that this one was a failing
[57:33] because of the client side rendering.
[57:37] Now let's bring up terminal and execute
[57:38] PHP artisan test. The browser testing
[57:41] needs a little bit more time. Uh yep
[57:44] this is not good. So we don't have
[57:46] assert status anymore. Instead we can
[57:48] replace this assert C. We expect to see
[57:51] Laravel. If we see Laravel we know this
[57:53] is good. and 200 status code was
[57:56] returned. Let's re-execute PHP artisan
[57:59] test.
[58:00] Now we see that the tests are actually
[58:02] passed. It shows welcome page. It shows
[58:05] the login page as well. So what we can
[58:08] do is call debug right here. Then
[58:12] execute this. And now it opened the
[58:14] browser and it stopped at point of
[58:18] debug. So it checked and saw that the
[58:21] login was existed there and it then it
[58:25] called the debug. So it paused open the
[58:27] browser so that we can debug what is
[58:29] happening right here in the following
[58:31] screen. I can zoom in this part. So this
[58:34] is it. Okay, we're going to close that.
[58:37] Press any kick to continue. If you want
[58:39] to see everything in a headed mode. So
[58:42] we have ability to provide um d- headed
[58:47] flag.
[58:49] So the browser opens, test is completed,
[58:51] it's closed, then it's open. So for
[58:53] every test, browser is opened and we see
[58:55] everything in the browser. However, it
[58:57] is fast and we don't actually notice
[58:59] what's going on. But that is browser
[59:01] testing. Of course, it is slightly
[59:04] slower than not non browser testing. the
[59:07] typical testing which is available in
[59:09] pest version 3. But the browser testing
[59:12] gives you ability to test uh everything
[59:15] actually in the browser like we are not
[59:17] able to test if the login page even
[59:19] opens in Laravel React version um of the
[59:23] starter kit but we are able to do this
[59:25] thing using browserbased testing. Now
[59:28] let's write a test to see if login
[59:31] actually works. So we're going to use uh
[59:34] it tests
[59:36] or um yeah it tests that login works.
[59:43] Okay. So we need to first create the
[59:46] user and then use that user for
[59:49] authentication. user equals uh new not
[59:53] new but the user factory create and we
[59:58] can provide password here.
[1:00:01] Password needs to be brypt of something
[1:00:05] like password
[1:00:07] one to three. Uh the user model needs to
[1:00:10] be imported from app models user. That's
[1:00:14] perfect. And now we can use that user's
[1:00:16] email and the password is one to three
[1:00:19] to actually type them in the browser. So
[1:00:22] we're going to use visit right now
[1:00:25] login. Actually we can do we can go even
[1:00:27] step further and we can do like this
[1:00:31] visit on slash
[1:00:34] then we're going to uh click
[1:00:38] we're going to click
[1:00:41] on the button which is called login. So
[1:00:43] if we access slash then we have the
[1:00:46] following button. So we're clicking on
[1:00:48] this and this is something this can be
[1:00:51] another test. So we uh we tested that
[1:00:54] when we access login URL we see this uh
[1:00:57] login text right there and we don't see
[1:00:59] dashboard. But another test inside here
[1:01:02] can be that when we click on slash and
[1:01:06] when we visit on slash then click on
[1:01:09] login then we see the login text and we
[1:01:13] can also check
[1:01:15] assert path is /lo. So the URL was
[1:01:21] changed into login. So that's also a
[1:01:24] good test which shows the login page
[1:01:26] that's for showing it right. So in our
[1:01:29] case we can directly visit to login. We
[1:01:32] know that now login page is opening. So
[1:01:35] this proves that the login page opens
[1:01:38] from homepage. Now when we visit login
[1:01:42] we can type email to be the user's email
[1:01:46] address. Whatever it is
[1:01:49] email we can type the password to be
[1:01:52] password one two three. And then we
[1:01:55] press a login button. Let's make sure
[1:01:57] that the button has the same text login
[1:02:00] right here. And then we check that the
[1:02:03] path needs to be dashboard right. So we
[1:02:06] save this. Let's bring up terminal and
[1:02:08] execute PHP artisan test. Let's remove
[1:02:11] this headed. It needs two three seconds.
[1:02:14] This error basically means that for just
[1:02:16] created user two factor authentication
[1:02:18] is enabled. And when we entered email
[1:02:22] and password in the form and hit the
[1:02:24] press button, it redirected us into the
[1:02:28] two factor page. So if we put a debug
[1:02:31] here and re-execute the same test, it
[1:02:35] will open debugger. Look at this. So and
[1:02:38] this is it. So now it expects us to
[1:02:41] enter two factor authentication code and
[1:02:44] the test was failed after some time.
[1:02:46] Okay. So in order to fix that we have to
[1:02:49] create the user without two factor
[1:02:51] authentication. If you open user factory
[1:02:55] uh right here inside the definition we
[1:02:57] have all the fields defined and we have
[1:02:58] two factor secret and two factory uh
[1:03:01] recovery codes and confirmed at. So that
[1:03:04] means that whenever we create the user
[1:03:06] those two factor fields are set. So we
[1:03:09] can actually override those fields for
[1:03:12] that specific user creation. we provide
[1:03:14] the password but we can also provide
[1:03:19] these fields. So this is going to be
[1:03:23] like this. So we provide null everywhere
[1:03:27] and now if we re-execute this test now
[1:03:30] the dashboard is visible and the test
[1:03:32] was passed. So and just like this if we
[1:03:34] want to test the two factor thing that's
[1:03:36] a different story but right now we
[1:03:38] tested that the if the two factor is not
[1:03:40] authentic is not activated on the user
[1:03:43] the user is created and the user is able
[1:03:45] to visit the login page enter those
[1:03:48] information click on login and the user
[1:03:50] is redirected into dashboard I'm going
[1:03:52] to remove this debug now besides this
[1:03:54] type and press let's have a look what
[1:03:57] other methods are available to interact
[1:04:00] with elements so we have click we have
[1:04:03] text. Uh this text basically method gets
[1:04:06] the text of the element which matches
[1:04:08] the given selector. So it simply returns
[1:04:10] the text. So finally we're going to get
[1:04:13] the text right here. This one returns
[1:04:15] the attribute. Let's have a look. So we
[1:04:17] have check to check the checkbox uh with
[1:04:20] a specific value.
[1:04:23] We have ability to check radio as well.
[1:04:28] We have ability to attach a file to the
[1:04:31] input type file. The press basically is
[1:04:33] the same as click. We also have
[1:04:36] possibility to select something in a
[1:04:38] selector. So in the country selector, we
[1:04:41] can provide us if it supports multiple
[1:04:44] values. We can do this in the following
[1:04:45] way. We have we have ability to submit
[1:04:49] the page. If the page has form inside
[1:04:52] there, we have ability to get the value.
[1:04:54] So we have a lot of things basically
[1:04:56] what we need to interact with the uh
[1:04:58] element. So we also have ability to get
[1:05:01] the current URL of the page to wait for
[1:05:03] some time. Um and u this is pretty cool
[1:05:08] also regarding this debugging test we
[1:05:11] have already seen debug method. Let's
[1:05:14] have a look at this screenshot method
[1:05:16] which has two properties to take the
[1:05:18] full page screenshot and to provide the
[1:05:21] file name as well. So I'm going to open
[1:05:24] now and let's provide screenshot here
[1:05:27] and I'm going to just leave this with
[1:05:30] default values. So we can execute this.
[1:05:33] It's going to take the screenshot and
[1:05:36] save it.
[1:05:38] Okay, the screenshots are typically
[1:05:41] inside tests browser screenshot and
[1:05:45] let's click on this and this is it. So
[1:05:47] it gave us the screenshot that the login
[1:05:49] works. Optionally we can provide the
[1:05:51] file name custom file name or the full
[1:05:54] page screenshot can be taken as well or
[1:05:56] we also have this screenshot element u
[1:06:00] method as well and it's going to take
[1:06:02] the screenshot of a specific element not
[1:06:03] the entire page we also have ability to
[1:06:06] open tinker at specific point of the
[1:06:09] code if the code is failing at some time
[1:06:11] because something is not correct in the
[1:06:13] database we can open the tinker at that
[1:06:15] point and observe the database and see
[1:06:18] what's there and uh we covered that we
[1:06:21] can open this in a headed mode as well
[1:06:24] if we want. So
[1:06:27] now let's have a look at some of the
[1:06:29] assertions that is available inside
[1:06:31] browsers API browser testing API. So we
[1:06:35] have ability to test if the page title
[1:06:37] is specific thing or title contains
[1:06:40] something. We can check if the checkbox
[1:06:42] is checked or not. We can see if the
[1:06:46] radio is selected or not. We can test if
[1:06:49] the element has a specific attribute or
[1:06:52] if the attribute is missing. We can see
[1:06:54] if the button is enabled um or not. We
[1:06:58] can see if the URL is a specific thing
[1:07:00] or not. So we have a lot of things
[1:07:02] basically what you think. We can check
[1:07:04] how many items basically of a specific
[1:07:07] selector we have in the DOM. Just have a
[1:07:10] look and understand what is available
[1:07:13] here. So we can also check if the
[1:07:15] screenshot matches previously taken
[1:07:17] screenshots which which is insane. So
[1:07:20] that's that one is uh like a really
[1:07:22] good. Now let's test if our application
[1:07:25] opens this cyber menu on mobile version.
[1:07:29] So we're going to open
[1:07:33] and it
[1:07:35] uh tests that mobile menu works.
[1:07:42] Okay, great. First, what we're going to
[1:07:44] do is create the user. So, I'm going to
[1:07:47] copy the following part and then we're
[1:07:49] going to login into the dashboard as
[1:07:51] well. So, I'm going to copy the
[1:07:52] following part completely. Paste here.
[1:07:55] So, once we are inside the dashboard, we
[1:07:56] can remove this screenshot part. I don't
[1:07:58] want the screenshot to be taken. So,
[1:08:00] once we are inside the dashboard already
[1:08:04] and we checked, then we're going to
[1:08:06] click on the following element. That
[1:08:09] element has data slot sidebar sidebar
[1:08:14] trigger. So we're going to select an
[1:08:17] element based on this.
[1:08:20] Let's search for press
[1:08:25] data slot equals sidebar toggle.
[1:08:30] Cool. So we press on this and then we
[1:08:33] expect something to be visible such as
[1:08:37] we expect platform to be visible or we
[1:08:39] can even trust the following text
[1:08:43] Laravel starter kit. This is a text I
[1:08:45] think Laravel starter kit to be visible
[1:08:51] visible assert visible Laravel starter
[1:08:54] kit and finally let's call debug to see
[1:08:58] what happens in the browser.
[1:09:03] Now we have been authenticated actually
[1:09:06] uh my bad. We should execute this on
[1:09:08] mobile. That's the thing. So we're going
[1:09:10] to call on then we're going to call
[1:09:13] mobile. Now we save and execute that.
[1:09:18] Now this was executed on mobile but it
[1:09:23] does not press on it. So I guess the
[1:09:26] test is going to fail.
[1:09:29] Okay, it was failed because
[1:09:35] that data slot sidebar toggle press did
[1:09:38] not work because this is called sidebar
[1:09:41] trigger not sidebar toggle. Let's
[1:09:43] reexecute that. Now look at this. So
[1:09:45] this opened and laral starter key text
[1:09:48] is visible. So probably the test is
[1:09:50] going to pass. Here we go. Perfect. Now
[1:09:53] let's write another test in which case
[1:09:55] we will be accessing several pages on
[1:09:58] the website. I'm going to log out from
[1:10:00] here. We're going to access this
[1:10:02] homepage login and register pages as
[1:10:05] well. And we're going to check that
[1:10:07] there are no errors in the console and
[1:10:10] there are no JavaScript errors and no
[1:10:12] console locks basically. So let's write
[1:10:15] the test. It
[1:10:17] tests that there
[1:10:21] are no console logs and errors.
[1:10:28] Great. So let's write this function. Now
[1:10:32] we're going to access to several pages
[1:10:34] in the following way. We use visit but
[1:10:38] we're providing array right here. So we
[1:10:40] access slash
[1:10:42] then we access uh plugin then we access
[1:10:47] register. Okay three pages are enough.
[1:10:50] So then we can destructure that and take
[1:10:53] it as home
[1:10:57] login and register.
[1:11:00] All right. So then individually we can
[1:11:03] write some assertions on home login and
[1:11:06] register. On home we can see assert
[1:11:12] title to be Laravel starter kit or
[1:11:15] whatever is the title uh for the
[1:11:17] homepage. It is welcome dash laravel.
[1:11:22] Welcome dash space d- laravel. This
[1:11:26] should be the title for the home. Let's
[1:11:28] duplicate these two times. For login
[1:11:32] there should be log in Laravel
[1:11:36] log in Laravel and for registration it
[1:11:40] should be register Laravel register
[1:11:44] Laravel also on home as well as on
[1:11:51] basically uh let me do the following.
[1:11:56] I'm going to assign this into pages.
[1:11:58] Then I'm going to dstructure them from
[1:12:01] pages. And then on all pages I can do
[1:12:06] the following. Where's the register?
[1:12:07] Register is missing here. Okay. On all
[1:12:10] pages I can call assert no console logs
[1:12:15] plus assert
[1:12:18] no JavaScript errors. Let's bring up
[1:12:22] terminal and execute PHP artisan test.
[1:12:25] Actually I want to debug this as well.
[1:12:28] So I love debugging and seeing what's
[1:12:31] going on. So we can execute PHP and
[1:12:34] test. Let's see it is visiting multiple
[1:12:38] pages. So that's one one test by the
[1:12:41] way. And we have to remove debug here.
[1:12:44] We don't want debug here. Okay. So we
[1:12:47] can execute only that specific test if
[1:12:50] we provide only here. So we we need only
[1:12:54] the this test right now. PHP artisan uh
[1:12:58] test. Okay, let's see what's the what's
[1:13:00] the issue. Oh,
[1:13:04] semicolon is missing. Okay, let's
[1:13:07] execute that. Now, it opened multiple
[1:13:10] windows at the same time and it is
[1:13:13] checking the console logs internally and
[1:13:17] it expects us to press any key and
[1:13:20] something was failed. Actually it was
[1:13:23] failed in debug mode but it it is
[1:13:25] passing in the um headless mode. If we
[1:13:28] have a look at the browser testing
[1:13:30] documentation we also have ability to
[1:13:33] configure custom user agent when we are
[1:13:35] accessing the page. We also have
[1:13:37] possibility to spec to provide a
[1:13:39] specific a geo location to configure
[1:13:42] local or to configure the time zone. So
[1:13:44] all that is possible and again I
[1:13:48] encourage you to have a look at the
[1:13:49] browser testing documentation because
[1:13:51] you're going to learn a lot of things
[1:13:52] there. Right now we are only running the
[1:13:55] following test which I'm going to
[1:13:56] uncomment and I'm going to execute PHP
[1:13:59] test but we also have ability to provide
[1:14:01] d- parallel. This is going to execute
[1:14:05] our tests in parallel mode and this is
[1:14:08] going to significantly speed up the
[1:14:11] process of testing. If you have hundreds
[1:14:13] of tests in your application, we have
[1:14:16] several tests in this file and the file
[1:14:18] is called public pages test. It's not
[1:14:21] logical. So I'm going to move some of
[1:14:22] the tests from here into a separate
[1:14:25] file.
[1:14:27] We are going to take this test that
[1:14:29] login works. Test that mobile menu works
[1:14:32] as well. And the rest can be left here.
[1:14:37] So this is for guest users. This as well
[1:14:40] and this as well. So I'm going to bring
[1:14:41] up terminal and execute PHP artisan make
[1:14:45] test. We can call this out pages
[1:14:49] test.
[1:14:51] We hit the enter out pages test. We open
[1:14:55] that. I'm going to replace this with my
[1:14:58] tests
[1:15:00] and we need to make sure that user is
[1:15:04] imported dash parallel. This is going to
[1:15:08] execute our tests in parallel mode and
[1:15:11] this is going to significantly speed up
[1:15:14] the process of testing if you have
[1:15:16] hundreds of tests in your application.
[1:15:19] We have several tests in this file and
[1:15:21] the file is called public pages test.
[1:15:24] It's not logical. So I'm going to move
[1:15:25] some of the tests from here into a
[1:15:28] separate file.
[1:15:30] We are going to take this test that
[1:15:32] login works. test that mobile menu works
[1:15:36] as well and the rest can be left here.
[1:15:40] So this is for guest users this as well
[1:15:43] and this as well. So I'm going to bring
[1:15:45] up terminal and execute PHP artisan make
[1:15:48] test. We can call this out pages
[1:15:53] test.
[1:15:54] We hit the enter out pages test. We open
[1:15:58] that. I'm going to replace this with my
[1:16:01] tests.
[1:16:04] And we need to make sure that user is
[1:16:07] imported. If we have specific cases when
[1:16:10] we want to test databases, this is the
[1:16:13] lesson for you. So if you want to always
[1:16:16] refresh the database, drop migrations,
[1:16:19] reapply them, then you're going to use
[1:16:21] this refresh database class in your test
[1:16:24] configuration file, which by default is
[1:16:27] there. If you want your test to run on a
[1:16:30] MySQL database if you have something
[1:16:32] something specific which SQLite for
[1:16:36] which SQLite is not enough then you can
[1:16:38] create.esting
[1:16:40] file and configure the MySQL database
[1:16:43] from here. Okay.
[1:16:45] So if you want to create new records
[1:16:48] we're going to use user factory create
[1:16:50] inside our test. If you want to create
[1:16:52] multiple uh records then we can do in
[1:16:55] the following way. If we want to execute
[1:16:57] the entire seed file, then we can call
[1:17:00] this seed in our test. That's just for
[1:17:04] the information.
[1:17:06] Now let's see how we can test the model
[1:17:08] attributes. Let's say we have the user
[1:17:11] which has first and last and we provide
[1:17:14] first and last and then the user has
[1:17:17] composed column which is the combination
[1:17:19] of first and last and we write the
[1:17:22] following thing. expect you full name to
[1:17:26] be surah in my case. So in the following
[1:17:30] way we can test if the attributes match
[1:17:32] to the specific thing. Another example
[1:17:35] is to test the relationships. Let's say
[1:17:37] we have the user model which has a
[1:17:39] relation to posts model and we have post
[1:17:42] model and migration defined in our
[1:17:44] database. So using factories we create
[1:17:47] user which has posts three. So this is
[1:17:50] going to create the user with three
[1:17:52] posts
[1:17:54] and if everything is set up properly and
[1:17:57] this is exactly what we are testing here
[1:18:00] then user posts need to have three
[1:18:03] count. So there will be exactly three
[1:18:06] posts for this user and because we are
[1:18:08] refreshing the database every time when
[1:18:09] we execute the test there will not be
[1:18:12] any additional posts in the database and
[1:18:14] the user and its posts are created right
[1:18:17] here. Okay, the next thing is to test if
[1:18:21] the post belongs to a specific user. In
[1:18:23] this case, we do it in a reverse order.
[1:18:25] So we create post but we create this for
[1:18:28] a user. So the user will also be
[1:18:31] created. And to test the relation if it
[1:18:34] works or not. Then from the post we
[1:18:37] access user property and check that the
[1:18:39] user not to be null.
[1:18:43] Okay. If we want to test the query
[1:18:45] scopes, let's say we have defined a
[1:18:47] published scope on the uh post model,
[1:18:52] then we create two posts. First, we
[1:18:55] create with a published property true.
[1:18:57] Second, we create with published
[1:18:59] property false. And when we select all
[1:19:03] posts with published scope, we expect
[1:19:06] the count to be one. And in the
[1:19:08] following way we will be able to test
[1:19:10] any scopes what you have defined for
[1:19:13] your project. You create records for the
[1:19:15] to satisfy the scope criteria and to not
[1:19:19] satisfy the scope criteria as well.
[1:19:23] Okay. Next if we want to test the CRUD
[1:19:26] for a specific model. So we create the
[1:19:29] product actually not create but instead
[1:19:32] we take the array of this product. So
[1:19:35] user factory create actually creates
[1:19:37] that in the database. But user factory
[1:19:39] make simply returns the model. It
[1:19:43] doesn't save it into the database. So
[1:19:45] then we call this product to array to
[1:19:48] convert this into an associated array
[1:19:49] and we call product create to create
[1:19:52] that in the database. Why we do this? We
[1:19:54] don't use a directly factory create
[1:19:57] because we want to test if the uh
[1:20:00] fillable properties are set correctly on
[1:20:05] the product model. So instead of product
[1:20:07] factory create we use product create
[1:20:10] which is the way we would typically use
[1:20:15] in our uh controller where we get this
[1:20:18] validated data and we pass this inside
[1:20:21] create method. So in this case if some
[1:20:23] of the fillable properties are not set
[1:20:26] up on the product model or something is
[1:20:29] wrong that product create will not be
[1:20:32] will not be working and thus the expect
[1:20:35] product count to be one will fail as
[1:20:38] well. So in the following way we take
[1:20:40] this product which we just created we
[1:20:42] perform update for the price and we then
[1:20:45] refresh that product. So we reselect
[1:20:48] that product from the database and we
[1:20:51] expect that the new product's price to
[1:20:54] be 200. And finally we delete the
[1:20:57] product and we expect the product count
[1:21:00] in the database to be zero. Okay. Next
[1:21:04] let's uh test soft deletes as well. If
[1:21:07] we have a post which has deleted at
[1:21:09] column in the database and we want to
[1:21:11] test if it's soft deleted properly. We
[1:21:14] create a post. We call soft we call
[1:21:17] delete on the post and then we use the
[1:21:20] following assertion assert soft deleted
[1:21:24] and we provide the table name and we
[1:21:26] provided the ID of the post we just
[1:21:30] deleted and then assert soft deleted
[1:21:33] will check that the post exists in the
[1:21:34] database and it has this deleted at
[1:21:37] column set after which we call product
[1:21:40] restore which means the deleted at
[1:21:43] becomes null again in the database and
[1:21:45] Then when we reselect this post with a
[1:21:47] fresh method, we ch we check that
[1:21:50] deleted at to be null. Okay, perfect.
[1:21:53] Next is to um next and I this is the
[1:21:57] final thing to check several assertions
[1:21:59] in the database. So we can use assert
[1:22:02] database has or assert database missing
[1:22:05] and provide the table name and the
[1:22:08] parameters based on which we're going to
[1:22:10] check if there is any user in the
[1:22:12] database which has email test
[1:22:14] atample.com and the second test will
[1:22:16] check if that there must be orders uh
[1:22:21] with canceled status missing in the
[1:22:23] database. So this one will pass if user
[1:22:27] exists with email testample.com. This
[1:22:30] one will pass if there are no orders
[1:22:33] with status canceled in the database.
[1:22:35] Finally, I'm going to give you a couple
[1:22:36] of recommendations. If you are working
[1:22:39] with a database and testing some models
[1:22:41] and relations, I recommend to use
[1:22:44] refresh database to always start with a
[1:22:47] clean data and use factories everywhere.
[1:22:50] um and test the behavior such as scopes,
[1:22:53] relationships and not eloquent internals
[1:22:57] and write small and atomic tests as
[1:23:00] well. Now let's talk about hooks local
[1:23:03] hooks as well as global hooks which can
[1:23:06] be executed once per file or once
[1:23:09] globally or once per test. If we have a
[1:23:13] look here, we have two tests and in both
[1:23:17] cases we are creating a user without
[1:23:20] two-actor authentication. First of all,
[1:23:22] if we open this two-factor, we have this
[1:23:24] without two factor method. So I can take
[1:23:26] this I can remove these three lines and
[1:23:30] I can use the method here. Next I am
[1:23:34] going to define before
[1:23:37] before each. before each is executed
[1:23:40] before every test in this particular
[1:23:44] file. So in this case I'm going to
[1:23:45] create this user equals I'm going to cut
[1:23:49] this out and paste here. So I'm creating
[1:23:52] a user assigning into this user and now
[1:23:55] I can use this user here. Scroll down
[1:23:59] below. I can remove this part completely
[1:24:01] and I can use this user here as well.
[1:24:05] Now let's bring terminal and execute PHP
[1:24:08] artisan test
[1:24:11] and we're going to see
[1:24:13] the out pages test are passing. Perfect.
[1:24:17] We also have before all. Before all is
[1:24:22] executed once in this file and before
[1:24:24] each is executed for each test file. We
[1:24:28] can also add after each and after all in
[1:24:32] the exact same way. However, we also
[1:24:35] have global hooks. If we open this past
[1:24:37] configuration file, we can use past
[1:24:39] function here. And then we can add
[1:24:42] before each.
[1:24:44] We can also add before all,
[1:24:49] after each,
[1:24:52] and after all.
[1:24:55] And the thing is that before each is
[1:24:57] executed for every test inside our
[1:25:00] project. before all is executed for
[1:25:02] every test file in our project. After
[1:25:05] each and after all works in the same way
[1:25:07] and if we have a global hook defined
[1:25:09] here and local hook defined here first
[1:25:12] global before each is executed this one
[1:25:16] then local before each in the same way
[1:25:18] before all works but first
[1:25:21] after each local after each is executed
[1:25:25] and then the global one and in the same
[1:25:27] way after all works. first local after
[1:25:30] all is executed then the global one and
[1:25:33] those are handy callback functions hooks
[1:25:35] if you want to execute specific code
[1:25:37] such as creating a user or deleting a
[1:25:40] directory after all as an example you
[1:25:43] can delete the directory from um from
[1:25:45] the file system uh before all you can
[1:25:48] execute a specific artisan command to
[1:25:51] run migrations or to create some data in
[1:25:54] the database so you can do this inside
[1:25:58] these hooks Now I'm going to talk about
[1:26:00] data sets and data providers. So if we
[1:26:04] want to execute test with a specific
[1:26:06] data set, we can use method called with
[1:26:09] right here and provide data. And we have
[1:26:11] different variations how we're going to
[1:26:13] provide the data. I'm going to provide
[1:26:16] an array here. Then inside an array I'm
[1:26:18] going to provide three elements one,
[1:26:20] two, and three.
[1:26:22] another set of numbers like five,
[1:26:27] seven and 12 and we can continue like
[1:26:30] this but that's enough I think. Now the
[1:26:32] thing is that pest will take each
[1:26:36] element right here in the given uh array
[1:26:39] global array this array. So this will be
[1:26:42] taken destructured and given into this
[1:26:45] function as three variables like a b and
[1:26:51] c. So a is going to be one, b is going
[1:26:55] to be two, c is going to be three. then
[1:26:57] this function will be executed second
[1:26:59] time for the second element of this
[1:27:01] array. So what we can do is write simple
[1:27:04] expect
[1:27:06] expect a plus
[1:27:09] a + b
[1:27:13] to be c just like this. So let's execute
[1:27:19] only this test.
[1:27:22] PHP artisan
[1:27:26] test.
[1:27:27] Now look at this. The two tests were
[1:27:30] executed basically in the following
[1:27:32] function because this is executed two
[1:27:34] times for a different data set. So let
[1:27:37] me duplicate this. Actually I'm going to
[1:27:39] create new one. So let's say we want to
[1:27:42] validate multiple emails which we have
[1:27:45] taken from a file or from a database.
[1:27:48] All right. So I'm going to use test
[1:27:51] validates
[1:27:54] emails. So then we have a function here
[1:27:57] and then I'm going to provide the width
[1:28:00] pass an array and then we can take
[1:28:05] um let's give it some key like this
[1:28:08] going to be anything like email one okay
[1:28:12] then we can have an array here this is
[1:28:15] going to be zuracample.com
[1:28:18] so we can have email two as well and
[1:28:22] email two let's say is not a valid
[1:28:25] email. It is like this. So we are
[1:28:27] providing these two elements inside with
[1:28:30] then right here we're accepting an
[1:28:32] email. All right. So test will um take
[1:28:37] uh actually let's dump let's dd what is
[1:28:40] the email here. So we're going to better
[1:28:43] understand how it works. So only
[1:28:47] we're going to remove this and execute
[1:28:50] that. And now look at this. Exactly as
[1:28:52] we expect. So pest is smart enough to
[1:28:54] take this value when we are expecting
[1:28:57] right here only one thing take this
[1:28:59] value and take its first element and
[1:29:02] give it as a first variable. So if we
[1:29:04] have a different elements in the
[1:29:06] following array they will be given as
[1:29:08] second and third variables right here.
[1:29:11] So after this we can write a simple
[1:29:13] filter
[1:29:15] var like the basic PHP one. We take the
[1:29:18] email filter validate email not to be
[1:29:21] false. Now if we execute this we expect
[1:29:25] uh it to be failed for something for
[1:29:28] email two because this is not a valid
[1:29:30] email. However if we make this
[1:29:33] valid email
[1:29:35] then we reexecute this we're going to
[1:29:37] see that both are passing. So we also
[1:29:40] have possibility to define data set with
[1:29:42] a name. As an example I'm going to use
[1:29:46] data set users and provide those two
[1:29:49] users. Now I can define test
[1:29:55] has a valid string as an example
[1:30:02] function.
[1:30:04] We're going to execute the following
[1:30:06] test with users data set. And right here
[1:30:10] we're going to expect name. That name
[1:30:12] will be Zura for the first execution,
[1:30:15] John for the second execution. And then
[1:30:16] we can write expect name to be string
[1:30:20] and we can execute only
[1:30:23] this test.
[1:30:27] Actually the other one was executed but
[1:30:29] we can see that has a valid string Zura
[1:30:32] and John. So this also works. We can
[1:30:36] also have uh inside this data set
[1:30:39] functions callback functions. I'm going
[1:30:40] to define another data set
[1:30:43] uh in this case that will be numbers
[1:30:48] and provide an array but I'm going to
[1:30:50] define a callback functions like fn ar
[1:30:53] error functions and we're taking a
[1:30:55] random integer or let's simply use rend
[1:30:59] uh from 1 to 10 this is the first one I
[1:31:03] have syntax error duplicate this and
[1:31:06] let's take second one from 10 to 20. So
[1:31:11] we have defined this data set. Then we
[1:31:13] use test
[1:31:16] to test if this has a valid number. We
[1:31:18] are executing the following test with
[1:31:21] numbers data set.
[1:31:24] And we expect each number received right
[1:31:26] here to be an integer. So I'm going to
[1:31:31] skip this one and skip this one. And I'm
[1:31:35] going to execute only this test. Let's
[1:31:41] execute it.
[1:31:44] What happened?
[1:31:48] Now look at this. Uh has a valid number
[1:31:51] with with the closure. So this is a
[1:31:53] closure. Um but the thing is that this
[1:31:55] is tested to be a valid number. And to
[1:31:57] show this I'm going to use dump here.
[1:32:00] Dump number. We save and execute that.
[1:32:04] And now look at this. One and 17. So
[1:32:06] those are random numbers uh generated by
[1:32:10] the following function. Now let's see an
[1:32:12] example of snapshot testing and
[1:32:15] screenshot testing. That's interesting
[1:32:17] one. So we have one test which snapshots
[1:32:21] a homepage and this works in a very
[1:32:24] straightforward way. We make request we
[1:32:26] get the response and then on that
[1:32:29] response content we expect to match
[1:32:32] snapshot. So if we execute this very
[1:32:35] first time we expect the following
[1:32:36] result. Snapshot created at the
[1:32:40] following location. There is no previous
[1:32:43] snapshot to compare with and to test
[1:32:46] this but it created snapshot right now
[1:32:49] when we executed this. And if we go
[1:32:51] inside test folder pass snapshots
[1:32:54] feature snapshot test. We can open this.
[1:32:56] And here it contains the entire response
[1:32:59] content what was returned. So this is
[1:33:01] the current snapshot. Right now this is
[1:33:04] a Laravel React single page application.
[1:33:07] That's why this is not a typical HTML
[1:33:10] serverside response content. Okay. But
[1:33:13] if you have a typical like a blog or
[1:33:16] serverside rendered content then um the
[1:33:19] HTML right here would probably contain
[1:33:22] uh like blog posts and so on. So we take
[1:33:25] this snapshot and then next time we
[1:33:28] execute this and it's going to take that
[1:33:31] response content and compare it to the
[1:33:34] previously made snapshot and if there is
[1:33:37] some difference it's going to report us.
[1:33:40] If the snapshot is the same it is going
[1:33:42] to simply pass the test. That's a great
[1:33:45] test uh to simply check that it
[1:33:47] mistakenly you mistakenly doesn't change
[1:33:50] the output content. So right now this is
[1:33:52] failing because uh as I mentioned this
[1:33:55] is more dynamic content and you can see
[1:33:58] that here we have some dynamic versions
[1:34:01] and things like which which changes on
[1:34:03] every refresh basically and that's why
[1:34:05] we see a different content but that test
[1:34:08] would be ideal to test some server side
[1:34:11] rendering um
[1:34:14] pages with great SEO. Not the one uh I'm
[1:34:17] showing right now. For the one I'm
[1:34:19] showing right now, we need to use
[1:34:23] browser testing, which I'm going to show
[1:34:24] you uh in a second.
[1:34:27] Okay, this is also a great way to test
[1:34:30] API endpoints. So, we make the request
[1:34:32] into our API. Let's say we get the um
[1:34:36] user information uh a specific single
[1:34:39] user information by ID or something.
[1:34:41] Then on the second time, we make the
[1:34:44] request and we are sure that we don't
[1:34:45] change anything. uh we don't remove
[1:34:47] mistakenly some fields from the uh
[1:34:50] resource of the user or we mistakenly
[1:34:52] didn't add something there. Now let's
[1:34:55] see another example which is more visual
[1:34:58] regression testing using screenshots.
[1:35:01] I'm going to use test here uh and let's
[1:35:04] give it the following description
[1:35:06] matches homepage
[1:35:10] screenshot. Okay. Now we are not using
[1:35:13] snapshot as an HTML but we're going to
[1:35:14] use screenshot. Okay, I'm going to skip
[1:35:18] the above test and I'm going to only
[1:35:21] execute this one. We are going to call
[1:35:24] visit here provide slash. We're
[1:35:26] accessing the homepage and then we are
[1:35:29] going to call method assert
[1:35:33] screenshot matches. Now, if we execute
[1:35:36] this, we're going to see that it is
[1:35:39] going to create this snapshot in the
[1:35:41] following location. Now, let's bring up
[1:35:43] this snapshot and open this matches
[1:35:46] homepage screenshot and we can see that
[1:35:50] this is how that snapshot looks like.
[1:35:53] All right. So now if we execute the same
[1:35:56] test again, now we're going to see that
[1:35:59] the test is passing because we haven't
[1:36:01] changed anything. So, the snapshot was
[1:36:05] failing, but this one actually was
[1:36:08] passed. Now, let's open welcome.tsx
[1:36:12] and let's find this text. Let's get
[1:36:14] started, which by the way is displayed
[1:36:16] right here. And I'm going to add one one
[1:36:19] right here. And we can make this text
[1:36:22] red as well. Okay, awesome.
[1:36:28] Text red 500. Okay, let's do like this.
[1:36:32] Okay, awesome. Now let's bring up
[1:36:34] terminal and execute PHP artisan test.
[1:36:39] We hit the enter.
[1:36:41] Now the test is going to fail. And it
[1:36:45] also gives us the following um
[1:36:47] suggestion. So if the failing is
[1:36:50] expected and if the change we made on
[1:36:52] our welcome.dtsx DSX file is
[1:36:54] intentional. We can execute the same
[1:36:57] command with d-update screenshot and it
[1:37:00] is going to take the current version as
[1:37:03] this stable version. Otherwise, it
[1:37:06] generated difference which we can find
[1:37:08] inside browser screenshots image diff
[1:37:11] view and this HTML file. If you open
[1:37:13] this HTML file, this is a typical HTML
[1:37:17] and let's open this in the browser. You
[1:37:20] can take this and open in a browser and
[1:37:22] let's click on this slider and look at
[1:37:24] this. So we have this nice slider view
[1:37:26] and here's the difference. Look at this.
[1:37:29] Let's get started 111 in red. Previously
[1:37:32] we had let's get started in black. So
[1:37:35] this is the difference that has been
[1:37:37] made and this is pretty cool. Now if I
[1:37:39] decide that this one is the stable
[1:37:41] version and I want to use this one. Now
[1:37:43] I can execute PHP artisan test with
[1:37:46] d-update snapshots. it is going to
[1:37:49] update snapshots in the in the file
[1:37:51] system. The test will not be completed
[1:37:55] at all. And the next time if I execute
[1:37:57] PHP artisan test, we're going to see
[1:37:59] that the test is passed because the
[1:38:01] latest version is this what we have
[1:38:03] currently and nothing has been changed
[1:38:05] after this latest version. Test also
[1:38:08] gives us possibility to group our tests
[1:38:10] into a specific group and then execute
[1:38:13] only that group. As an example, we have
[1:38:17] this snapshot test and out pages test.
[1:38:20] So what I can do instead of calling this
[1:38:22] skip and only. So I'm going to remove
[1:38:24] both from here. And let's say I want to
[1:38:28] execute only these two tests. I'm going
[1:38:30] to assign group to them. And I'm going
[1:38:33] to call these snapshots. Snapshots here.
[1:38:37] And snapshots here as well. Actually I'm
[1:38:40] going to use snapshots here as well. Now
[1:38:42] I'm going to bring up terminal. Then I'm
[1:38:44] going to execute PHP artisan test dash
[1:38:47] group equals snapshots. We hit the
[1:38:51] enter. And now only those two tests will
[1:38:54] be executed. The first one is snapshot
[1:38:56] homepage which was created and the
[1:38:59] second is matches homepage screenshot
[1:39:01] which was passed. Now if you execute
[1:39:03] this second time both should be passed
[1:39:05] because the first one actually the first
[1:39:08] one is failing. Yeah, we we know why
[1:39:10] this is failing because it has a dynamic
[1:39:13] content in a response. But the thing is
[1:39:15] that we have flexibility to um assign
[1:39:19] groups and then execute based on groups.
[1:39:22] Actually we also have ability to assign
[1:39:24] multiple groups to our tests. So I can
[1:39:28] provide an array here. Let's say this is
[1:39:32] snapshots and home as well. And this is
[1:39:36] snapshots. And we can assign something
[1:39:40] else such as screenshots. So we can
[1:39:44] execute tests based on one group or the
[1:39:47] second group. It's up to you. So if I
[1:39:50] try to execute
[1:39:52] let's say
[1:39:54] screenshots, it is going to execute only
[1:39:57] this test.
⚡ Saved you time reading this? Transcribe any YouTube video for free — no signup needed.