├── .gitignore
├── CONTRIBUTING.md
├── README.md
├── README_VN.md
├── en
├── Conventions.md
├── Integration
│ └── Integration.md
├── Introduction.md
├── Knowledge.md
└── Unit
│ ├── Common.md
│ ├── Controllers.md
│ ├── Middleware.md
│ ├── Models.md
│ ├── Transformers.md
│ └── Validation.md
└── vn
├── Conventions.md
├── Integration
└── Integration.md
├── Introduction.md
├── Knowledge.md
└── Unit
├── Common.md
├── Controllers.md
├── Middleware.md
├── Models.md
├── Transformers.md
└── Validation.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contribute to the project
2 | - If you have any subjects and want to discuss with us, please create a ticket at the [issues section](../../issues/)
3 | - If you want to correct mistakes, or add new information to the document, please submit a new Pull Request.
4 | - A Pull Request that contains both English and Vietnamese is really appreciated.
5 |
6 | All kind of contributions to the project are always welcome :+1:
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Framgia's Laravel Project Testing Guidelines
2 |
3 | *For Vietnamese version of this document, please refer [this file](./README_VN.md)*
4 |
5 | Guideline for writing tests in a Laravel Project
6 |
7 | - [Introduction](./en/Introduction.md)
8 | - [Knowledge](./en/Knowledge.md)
9 | - [Conventions](./en/Conventions.md)
10 | - [Laravel Test Examples Project](https://github.com/framgia/laravel-test-examples)
11 |
12 | # Guidelines
13 | ### Unit Tests
14 | - [Models](./en/Unit/Models.md)
15 | - [Controllers](./en/Unit/Controllers.md)
16 | - [Transformers](./en/Unit/Transformers.md)
17 | - [Validation](./en/Unit/Validation.md)
18 | - [Middleware](./en/Unit/Middleware.md)
19 | - [Common](./en/Unit/Common.md)
20 |
21 | ### Integration Tests
22 | - [Integration](./en/Integration/Integration.md)
23 |
--------------------------------------------------------------------------------
/README_VN.md:
--------------------------------------------------------------------------------
1 | # Framgia's Laravel Project Testing Guidelines
2 |
3 | *For English version of this document, please refer [this file](./README.md)*
4 |
5 | Hướng dẫn về việc viết Tests cho project Laravel.
6 |
7 | - [Giới Thiệu](./vn/Introduction.md)
8 | - [Kiến Thức](./vn/Knowledge.md)
9 | - [Quy ước](./vn/Conventions.md)
10 | - [Laravel Test Examples Project](https://github.com/framgia/laravel-test-examples)
11 |
12 | # Guidelines
13 | ### Unit Tests
14 | - [Models](./vn/Unit/Models.md)
15 | - [Controllers](./vn/Unit/Controllers.md)
16 | - [Transformers](./vn/Unit/Transformers.md)
17 | - [Validation](./vn/Unit/Validation.md)
18 | - [Middleware](./vn/Unit/Middleware.md)
19 | - [Common](./vn/Unit/Common.md)
20 |
21 | ### Integration Tests
22 | - [Integration](./vn/Integration/Integration.md)
23 |
--------------------------------------------------------------------------------
/en/Conventions.md:
--------------------------------------------------------------------------------
1 | # Laravel Testing Conventions
2 |
3 | ## Folder Structure
4 | - All Unit Tests MUST be placed inside the `tests/Unit` folder
5 | - All Integration Test MUST be placed inside the `tests/Feature` folder
6 | - Contents of the `Unit` and `Feature` folders MUST have the same structure with the main `app` folder. For example, unit tests for file `app/Models/User.php` MUST be written in `tests/Unit/Models/UserTest.php`
7 |
8 | ## Naming Convention
9 | - Each test file MUST have a specific namespace. The namespace MUST start with `Tests\` (or `ASampleProjectTests\`), then followed by the folder structure. For example, namespace for `tests/Unit/Models/UserTest.php` file MUST be `Tests\Unit\Models\UserTest` (or `ASampleProjectTests\Unit\Models\UserTest`)
10 | - For better readability, use `snake_case` for test function's name. A test function has to start with `test`, so here is an example:
11 | ```
12 | public function test_it_throws_an_exception_when_email_is_too_long()
13 | {
14 | }
15 | ```
16 | - Non-test function can stick with PSR-2 rule, that uses `camelCase` for function name.
17 |
18 | ## Unit Test Required Components
19 | - **Controllers**: with disabled events handling. All external components MUST be mocked.
20 | - **Requests** (if present): test validation
21 | - **Models**: getters, setters, additional features
22 | - **Transformers / Presenters** (if present): test output results for different source sets
23 | - **Repositories** (if present): test each method for creating correct SQL queries OR correct calls to mocked query builder
24 | - **Event listeners**
25 | - **Queue jobs**
26 | - **Auth policies**
27 | - And any additional project-specific classes
28 |
29 | ## Integration Test Required Components
30 | - **Routes**: test input/output with integration in a whole system
31 | - **Route authentication**
32 |
33 | ## Code Coverage
34 | - Code Coverage for the entire project SHOULD be greater than `80%`
35 |
--------------------------------------------------------------------------------
/en/Integration/Integration.md:
--------------------------------------------------------------------------------
1 | # Integration Tests
2 |
3 | Purpose of integration tests is to make sure that all components work together as needed.
4 |
5 | While making such tests, remember that changes to data storage or interactions with external services might happen.
6 | In order to handle such cases properly, you might still need to create [Test Doubles](../Knowledge.md#test-doubles) for some classes.
7 |
8 | Which classes should be doubled depends on your testing approach and might be different depending on app size.
9 |
10 | ## Guide
11 |
12 | Laravel provides 2 ways to create integration tests: making HTTP requests to routes and browser-based testing with
13 | `laravel/dusk` package.
14 |
15 | It is required to have HTTP tests, while browser tests don't have any specific requirements.
16 |
17 | Integration tests must cover EVERY route of application and complete following purposes:
18 |
19 | **HTTP**
20 | - Authentication and policy tests. Each case must have separate test assertion.
21 | - Check valid status codes for every type of response.
22 | - Check correct redirect codes and paths on different events.
23 | - Test validity of JSON responses.
24 | - Test Error handlers for correct responses.
25 | - Check validity of API versions (if necessary).
26 |
27 | **Database**
28 | - Ensure data is written to database correctly during requests.
29 | - Test migration process.
30 | - Test abnormal data insertions are handled properly.
31 |
32 | If your application interacts with external services and servers, you either need to use staging versions of
33 | such services or mock your client classes.
34 |
35 | Testing environment can be defined with special configurations and variables, e. g. `testing` database connection with
36 | `DB_TEST_DATABASE` env variable. However it is **strictly recommended** to replace production variables instead in
37 | phpunit.xml or CI configuration.
38 |
39 | It is good to run tests twice with `APP_ENV` set both to `testing` and `production`.
40 |
41 | ```xml
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | ```
52 |
--------------------------------------------------------------------------------
/en/Introduction.md:
--------------------------------------------------------------------------------
1 | # Introduction to Laravel Testing
2 |
3 | ## Test Case
4 | - Test Case is a commonly used term for a specific test. This is usually the smallest unit of testing.
5 | - A Test Case will consist of information such as requirements testing (a set of inputs, execution preconditions), test steps, verification steps, prerequisites, outputs, test environment, etc.
6 |
7 | ## Unit Test
8 | - Unit testing is a software testing method by which **individual units** of source code are tested to determine whether they are fit for use.
9 | - In a Laravel project, writing Unit test is the procedure in which all test cases for **particular function/method** are witten.
10 | - Unit test should be designed to test the function **independently**. In other words: **`A` requires `B` to run. Even when `B` is broken, unit test of `A` SHOULD PASS if there are no problems with `A`.**
11 | - A good Unit test **SHOULD NOT**:
12 | - Trigger to run codes from other funtions in project
13 | - Hit the database
14 | - Use the file system
15 | - Access the network
16 | - ...
17 |
18 | ## Integration Test
19 | - Integration testing is the phase in software testing in which individual software modules are combined and tested as a group.
20 | - Unit tests help us to ensure that every single function works correctly. Integration tests help us to ensure that different parts of the project works flawlessly when combines with each others in the real life.
21 |
22 | ## PHPUnit
23 | - **PHPUnit** is a programmer-oriented testing framework for PHP. It is an instance of the xUnit architecture for unit testing frameworks.
24 | - [Installation Guide](https://phpunit.de/getting-started.html)
25 |
26 | ### Per-project installation
27 | ```
28 | $ composer require --dev phpunit/phpunit
29 | ```
30 |
31 | - Laravel project is already integrated with PHPUnit. You can start testing your App using the command:
32 | ```
33 | $ ./vendor/bin/phpunit
34 | ```
35 |
36 | > Local per-project installation is highly recommented as you might have dependencies on different versions of PHPUnit in different projects.
37 |
38 | ### Global installation
39 |
40 | - Via download:
41 | ```
42 | $ wget https://phar.phpunit.de/phpunit.phar
43 | $ chmod +x phpunit.phar
44 | $ sudo mv phpunit.phar /usr/local/bin/phpunit
45 | ```
46 | - Via composer:
47 | ```
48 | $ composer global require phpunit/phpunit
49 | ```
50 |
51 | Make sure you have added `/home//.composer/vendor/bin` or `c:\Users\AppData\Roaming\Composer\vendor\bin` to your `PATH` variable.
52 |
53 | - You can run globally installed PHPUnit from anywere:
54 | ```
55 | $ phpunit
56 | ```
57 |
58 | ## Code Coverage
59 | - **Code coverage** is a measure used to describe the degree to which the source code of a program is executed when a particular test suite runs. In other words, **code coverage** is a way of ensuring that your tests are actually testing your codes!
60 | - Code coverage fomular:
61 | ```
62 | Code Coverage = (Number of Lines of Code Called By Your Tests) / (Total Number of Relevant Lines of Code) x 100%
63 | ```
64 |
65 | For examples: If your code coverage is 90%, that means 90% of your codes in project have been called and run when testing.
66 | - Code Coverage can be generated by PHPUnit with **Xdebug** enabled. Therefore, please make sure that you have the Xdebug installed and enabled. Check the [Xdebug Installation Guide](https://xdebug.org/docs/install)
67 | - You can run PHPUnit and generate coverage report in HTML format by the following command:
68 | ```
69 | phpunit --coverage-html
70 |
71 | // Generate code coverage report in Clover XML format.
72 | phpunit --coverage-clover
73 | ```
74 | - Code Coverage is a really useful tool for finding untested codes in your project. However, it is not a powerful number that can state how good your tests are.
75 |
76 | ## Documents
77 | - [PHPUnit Manual](https://phpunit.de/manual/current/en/phpunit-book.pdf)
78 | - [Laravel Testing Official Documents](https://laravel.com/docs/master/testing)
79 | - [Laravel Testing Decoded](https://leanpub.com/laravel-testing-decoded)
80 | - Laracast's [Testing Jargon](https://laracasts.com/series/testing-jargon)
81 | - Laracast's [Testing Laravel](https://laracasts.com/series/phpunit-testing-in-laravel)
82 | - Laracast's [Intuitive Integration Testing](https://laracasts.com/series/intuitive-integration-testing)
83 |
--------------------------------------------------------------------------------
/en/Knowledge.md:
--------------------------------------------------------------------------------
1 | # Knowledge about Unit Testing
2 |
3 | ## Classify
4 |
5 | Before creating of any test cases, we should determine input values of **particular function/method** which need to be test. The test cases should be designed to cover all combinations of input values and preconditions. Basically, we usually divide types of test case by 3 types of input dataset for unit testing.
6 |
7 | - **Normal**: Inputs are normal range values (accepted). A large amount of codes can be covered just by running **normal** test cases.
8 | - **Boundary**: Inputs are at or just beyond its maximum or minimum limits. They are used to identify errors at boundaries rather than finding those exist in center of input domain.
9 | - **Abnormal**: Inputs are invalid (illegal or not expected) to test error handling and recovery.
10 |
11 | For example: *Suppose we have a function which validate email address entered by user. The maximum length of an email address is 50 characters.*
12 |
13 | ```php
14 | function validate($email) {
15 | if (filter_var($email, FILTER_VALIDATE_EMAIL) && strlen($email) <= 50) {
16 | return true;
17 | }
18 | return false;
19 | }
20 |
21 | ```
22 |
23 | we should write the test cases as below:
24 |
25 |
26 | Normal cases
27 |
28 | ```php
29 | public function test_valid_email_format_and_length()
30 | {
31 | // Email with length 18 (less than: maximum - 1)
32 | $email = 'sample@framgia.com';
33 | $this->assertEquals(true, validate($email));
34 | }
35 | ```
36 |
37 |
38 |
39 |
40 | Boundary cases
41 |
42 | ```php
43 | public function test_valid_email_format_and_length_max_minus()
44 | {
45 | // Email with length 49 (maximum - 1)
46 | $email = 'samplesamplesamplesamplesamplesamples@framgia.com';
47 | $this->assertEquals(true, validate($email));
48 | }
49 |
50 | public function test_valid_email_format_and_length_max()
51 | {
52 | // Email with length 50 (equal maximum)
53 | $email = 'samplesamplesamplesamplesamplesamplesa@framgia.com';
54 | $this->assertEquals(true, validate($email));
55 | }
56 |
57 | public function test_valid_email_format_and_length_max_plus()
58 | {
59 | // Email with length 51 (maximum + 1)
60 | $email = 'samplesamplesamplesamplesamplesamplesam@framgia.com';
61 | $this->assertEquals(false, validate($email));
62 | }
63 | ```
64 |
65 |
66 |
67 |
68 | Abnormal cases
69 |
70 | ```php
71 | public function test_invalid_email_format()
72 | {
73 | // Invalid email format with normal length (between 0 ~ 50)
74 | $email = 'framgia.com';
75 | $this->assertEquals(false, validate($email));
76 | }
77 |
78 | public function test_valid_email_format_and_length_exceeded()
79 | {
80 | // Email with length 54
81 | $email = 'samplesamplesamplesamplesamplesamplesample@framgia.com';
82 | $this->assertEquals(false, validate($email));
83 | }
84 | ```
85 |
86 |
87 |
88 | ## Test Doubles
89 | One of the fundamental requirements of Unit Test is **isolation**. In general, isolation is hard (if not impposible) as there are always dependencies across the whole project.
90 |
91 | Therefore, the concept of `Test Doubles` was born. A `Test Double` allows us to break the original dependency, helping isolate the unit.
92 |
93 | Here are some types of `Test Doubles`
94 |
95 | ***Some parts of following definitions are taken from Martin Fowler's Blog [Mocks Aren't Stubs](https://martinfowler.com/articles/mocksArentStubs.html)***
96 |
97 | ### Dummies
98 | - Dummy objects are passed around but never actually used. Usually they are just used to fill parameter lists.
99 |
100 | ### Fake
101 | - Fake objects actually have working implementations, but usually take some shortcut which makes them not suitable for production (an in memory database is a good example).
102 |
103 | ### Stubs
104 | - Stubs provide canned answers to calls made during the test, usually not responding at all to anything outside what's programmed in for the test.
105 |
106 | ### Mocks
107 | - Mocks are objects pre-programmed with expectations which form a specification of the calls they are expected to receive.
108 |
109 | ### Stubs vs Mocks
110 | - Stub helps us to run test. Otherwise, Mock is an object which runs the test.
111 | - A Fake which you verify calls against is a Mock. Otherwise, it's a Stub.
112 | - Stub can never fail the test. Otherwise, Mock can.
113 |
114 | ## Examples
115 | - Here are some PHP Mocking Frameworks that you can use to easily create Mocks for testing:
116 | - Mockery: It is highly recommended. It has been already integrated with Laravel Project. Document [here](http://docs.mockery.io/)
117 | - Prophecy: A part of PHPSpec project, but can be used outside PHPSpec. Check it [here](https://github.com/phpspec/prophecy)
118 | - Examples of creating Stubs and Mocks using **Mockery**
119 |
120 | // TODO
121 |
--------------------------------------------------------------------------------
/en/Unit/Common.md:
--------------------------------------------------------------------------------
1 | # Common Testing Rules
2 |
3 | With most components like events, event listeners, subscribers, queue jobs and validation rule classes, you should follow
4 | the same basic rules:
5 |
6 | 1. Test `__construct()` method to make sure that properties are assigned.
7 | 2. Cover all public methods with necessary tests.
8 | 3. Pay the most attention to the main method (e. g. `handle` for listeners or `passes` for rules).
9 | 4. Coverage is not necessary for methods, which return text (e. g. `message` for rules).
10 | 5. Coverage is optional for basic get/set methods, which don't modify input arguments.
11 |
12 | ## Mocking DI instances
13 |
14 | Laravel uses injections in various cases and your components might use it via method/constructor injection
15 | or directly inside methods via container instance or facades.
16 |
17 | For such cases it is very important to create test doubles for every injected instance and make sure that
18 | no additional instances are resolved.
19 |
20 |
21 | By default, `TestCase` class provided with Laravel automatically instantiates application instance. It is good
22 | to use this approach, but highly recommended to have an additional basic test case with mocked app.
23 |
24 | ```php
25 | class MockApplicationTestCase extends TestCase
26 | {
27 | /**
28 | * Creates the application.
29 | *
30 | * @return \Illuminate\Foundation\Application|\Mockery\Mock
31 | */
32 | public function createApplication()
33 | {
34 | return m::mock(\Illuminate\Foundation\Application::class)->makePartial();
35 | }
36 | }
37 | ```
38 |
39 | This approach will help you to control all DI process carefully. Now you can inject test doubles simply.
40 |
41 | ```php
42 | public function test_it_assignes_event_handler()
43 | {
44 | $e = m::mock(Dispatcher::class);
45 | $this->app->bind(Dispatcher::class, $e);
46 | // Test event assignment with mocked class.
47 | }
48 | ```
49 |
50 | ## Testing Database Queries
51 |
52 | Isolation is very important for test cases and it is critical to make sure that SQL queries are built properly.
53 |
54 | All database interactions in Laravel are made through `Illuminate\Database\Connection` class, which methods can be easily
55 | mocked with Mockery package.
56 |
57 | ```php
58 | $connection = m::mock(Connection::class);
59 | $query = new Builder($connection, new SQLiteGrammar(), new Processor());
60 | // DB::table is the most common call and it is simpler to mock the method manually instead of dealing with partial mocks.
61 | $connection->allows()->table()->andReturnUsing(function ($table) use ($query) {
62 | return $query->from($table);
63 | })
64 |
65 | // Test your queries
66 |
67 | $connection->shouldReceive('select')->with(
68 | 'select * from "streets" limit 10 offset 0', // query
69 | [], // bindings
70 | m::any() // useReadPdo. use true/false if necessary
71 | )->andReturn([
72 | // 'query result'
73 | ]);
74 |
75 | $someClass->methodUsingConnection();
76 | ```
77 |
78 | Sometimes it is handy to inject mocked connection into DI and have it available everywhere.
79 | Direct property modification is used here, because default extension requires mocking of connection methods,
80 | which we don't need.
81 |
82 | ```php
83 | protected $db;
84 |
85 | public function setUp()
86 | {
87 | $this->afterApplicationCreated(function () {
88 | $this->db = m::mock(
89 | Connection::class.'[select,update,insert,delete]',
90 | [m::mock(\PDO::class)]
91 | );
92 |
93 | $manager = $this->app['db'];
94 | $manager->setDefaultConnection('mock');
95 |
96 | $r = new \ReflectionClass($manager);
97 | $p = $r->getProperty('connections');
98 | $p->setAccessible(true);
99 | $list = $p->getValue($manager);
100 | $list['mock'] = $this->db;
101 | $p->setValue($manager, $list);
102 | });
103 |
104 | parent::setUp();
105 | }
106 | ```
107 |
108 | You should remember following things for database testing:
109 |
110 | - You may have named connections, be careful with that.
111 | - Models get connections via `ConnectionResolverInterface` which is assigned in the model itself and might differ.
112 | - Queries are created differently using different grammars. Use the one suitable for your project.
113 |
114 | ## Examples
115 |
116 | * [Laravel event](https://github.com/framgia/laravel-test-examples/tree/master/app/Events) and [tests](https://github.com/framgia/laravel-test-examples/tree/master/tests/Unit/Events)
117 | * [Listeners](https://github.com/framgia/laravel-test-examples/tree/master/app/Listeners) and [tests](https://github.com/framgia/laravel-test-examples/tree/master/tests/Unit/Listeners)
118 | * [Database mock setup](https://github.com/framgia/laravel-test-examples/blob/master/tests/Unit/Http/Controllers/CityControllerTest.php#L24) and [usage](https://github.com/framgia/laravel-test-examples/blob/master/tests/Unit/Http/Controllers/CityControllerTest.php#L76)
119 |
--------------------------------------------------------------------------------
/en/Unit/Controllers.md:
--------------------------------------------------------------------------------
1 | # Testing Controllers
2 |
3 | Controllers implement the main request processing algorithm. They must be paid the most attention during testing process.
4 |
5 | Unit tests written for controllers must be totally isolated and every external call from inside controller method must be mocked.
6 |
7 | Depending on your application logic, controllers might implement validation in method logic or via Form Requests.
8 | If first, controller tests must include cases with **[boundary](../Knowledge.md#Classify)** and **[abnormal](../Knowledge.md#Classify)** request input. Otherwise those cases should be
9 | tested in appropriate form request test cases.
10 | However, this affects only user input. Data provided from outside controller method must be mocked properly with all kinds of data.
11 |
12 | ## Guide
13 |
14 | ## Examples
15 |
16 | * [Resource web controller](https://github.com/framgia/laravel-test-examples/app/Http/Controllers/CityController.php) and [tests](https://github.com/framgia/laravel-test-examples/blob/master/tests/Unit/Http/Controllers/CityControllerTest.php)
17 |
--------------------------------------------------------------------------------
/en/Unit/Middleware.md:
--------------------------------------------------------------------------------
1 | # Testing Middleware Classes
2 |
3 | Middleware test cases are not different from most other classes. Except you need to pay more attention to `$next` callback.
4 |
5 | ## Guide
6 |
7 | You should remember that middleware might return data without calling `$next` and that might be an unexpected case.
8 | There are X cases of middleware execution:
9 |
10 | - Returns `$next` closure result: basic *before* middleware.
11 | - Returns response without calling `$next`: abortion middleware.
12 | - Receives response from `$next` and modifies it: basic *after* middleware.
13 |
14 | Depending on your implementation, these cases might be mixed (e. g. conditional abortion or before/after execution).
15 |
16 | In order to mock `$next` callback, you can simply create a closure with assertions within test case:
17 |
18 | ```php
19 | public function test_middleware_appends_header()
20 | {
21 | //...
22 | $next = function ($request) {
23 | $this->assertEquals('Appended_header_data', $request->headers->get('header-name'));
24 | return response('ok');
25 | }
26 |
27 | $middleware->handle($request, $next);
28 | //...
29 | }
30 | ```
31 |
32 | Sometimes middleware might interact with session, cache or other components. In this case you must mock those components
33 | properly.
34 |
35 | In the special case of session modifications, it is highly recommended to use `$request->getSession()` in method body
36 | instead of facades or dependency injection.
37 |
38 | ## Examples
39 |
40 | * [Middlewares](https://github.com/framgia/laravel-test-examples/tree/master/app/Http/Middleware)
41 | * [Middleware tests](https://github.com/framgia/laravel-test-examples/tree/master/tests/Unit/Http/Middleware)
--------------------------------------------------------------------------------
/en/Unit/Models.md:
--------------------------------------------------------------------------------
1 | # Testing Models
2 |
3 | Models are ones of mostly used classes in Laravel applications. And at the same time they are the most complicated
4 | from all default components.
5 |
6 | Depending on implementation, models might include validation, external services connection (e. g. ElasticSearch indexing),
7 | property getters and various custom methods.
8 |
9 | As far as models might be used in any part of application, they must be tested carefully.
10 |
11 | ## Guide
12 |
13 | ### Configuration properties
14 |
15 | Any model includes some default configurations with public or protected properties. These properties might affect
16 | application behaivor a lot, but they are the most easy to test.
17 | You can simply assert return values of that property getters:
18 |
19 | ```php
20 | public function test_contains_valid_fillable_properties()
21 | {
22 | $m = new User();
23 | $this->assertEquals(['name', 'email'], $m->getFillable());
24 | }
25 | ```
26 |
27 | Following method tests must be included in tests:
28 |
29 | - `$fillable` -> `getFillable()`
30 | - `$guarded` -> `getGuarded()`
31 | - `$table` -> `getTable()`
32 | - `$primaryKey` -> `getKeyName()`
33 | - `$connection` -> `getConnectionName()`: in case multiple connections exist.
34 | - `$hidden` -> `getHidden()`
35 | - `$visible` -> `getVisible()`
36 | - `$casts` -> `getCasts()`: note that method appends incrementing key.
37 | - `$dates` -> `getDates()`: note that method appends `[static::CREATED_AT, static::UPDATED_AT]`.
38 | - `newCollection()`: assert collection is exact type. Use `assertEquals` on `get_class()` result, but not `assertInstanceOf`.
39 |
40 | It is fine to put all checks inside one test method, e. g. `test_model_configuration()`
41 |
42 | ### Relations
43 |
44 | Relations are defined as methods returning appropriate relation classes. In some cases relation may be defined
45 | with custom foreign keys or additional query changes. The best way to test relation config is to assert values
46 | on returned `Relation` instance.
47 |
48 | In all default cases, any relations is being created with `$model->newQuery()` as builder instance. If not modified,
49 | it can be simply asserted with `assertEquals`
50 |
51 | ```php
52 | public function test_user_relation()
53 | {
54 | $m = new Post();
55 | $relation = $m->user();
56 |
57 | $this->assertInstanceOf(BelongsTo::class, $relation);
58 | $this->assertEquals('user_id', $relation->getForeignKey());
59 | $this->assertEquals('id', $relation->getOwnerKey());
60 | //...
61 | $this->assertEquals($m->newQuery(), $relation->getQuery());
62 | }
63 | ```
64 |
65 | Depending on relation type, following test asserts must be included:
66 |
67 | - `getQuery()`: assert query has not been modified or modified properly.
68 | - `getForeignKey()`: any `HasOneOrMany` or `BelongsTo` relation, but key type differs (see documentaiton).
69 | - `getQualifiedParentKeyName()`: in case of `HasOneOrMany` relation, there is no `getLocalKey()` method, so this one should be asserted.
70 | - `getOwnerKey()`: `BelongsTo` relation and its extendings.
71 | - `getQualifiedForeignPivotKeyName()`: `BelongsToMany` relation.
72 | - `getQualifiedRelatedPivotKeyName()`: `BelongsToMany` relation.
73 | - `getTable()`: `BelongsToMany` relation. Optional, because included in methods above.
74 | - `getMorphType()`: any polymorphic relations.
75 |
76 | ### Property values
77 |
78 | Many models might include property mutators or getters. Those methods modify output in many cases including `__get` and
79 | `toArray()` results.
80 |
81 | There must be a test case, which makes assertion on all properties.
82 | For such case, you can use `unguarded` helper or remove guards manually.
83 |
84 | ```php
85 | public function test_properties_have_valid_values()
86 | {
87 | User::unguard();
88 |
89 | $initial = [
90 | 'name' => 'Jonh Doe',
91 | 'email' => 'jonh@example.com',
92 | ];
93 |
94 | $m = new User($initial);
95 |
96 | $this->assertEquals($initial, $m->setHidden([]), $m->attributesToArray());
97 | }
98 | ```
99 |
100 | In case any getter exists, values might be modified, so tests should cover such additions.
101 | Use `getAttributeValue()` to make sure, getter is called and property, and call to getter itself to test different values.
102 | As far as mutators might also exist, use `setRawAttributes` to set initial values to ignore any unexpected value changes.
103 |
104 | ```php
105 | public function test_status_getter()
106 | {
107 | $m = new User();
108 | $m->setRawAttributes([
109 | 'status' => 1,
110 | ]);
111 |
112 | // Test if getter is working.
113 | $this->assertEquals('enabled', $m->getAttributeValue('status'));
114 |
115 | // Test getter logic with different values.
116 | $this->assertEquals('disabled', $m->getStatusAttribute(2));
117 | $this->assertEquals('pending', $m->getStatusAttribute(3));
118 |
119 | // Abnormal case
120 | $this->expectException(\InvalidArgumentException::class);
121 | $m->getStatusAttribute(4);
122 | }
123 | ```
124 |
125 | Same rules apply to mutators. In order to avoid any getters applied, use `getAttributes()` method for assertions.
126 |
127 | ```php
128 | public function test_status_getter()
129 | {
130 | $m = new User();
131 |
132 | // Test if mutator is working.
133 | $m->setAttribute('status', 'enabled');
134 | $this->assertEquals(1, $m->getAttributes()['status']);
135 |
136 | // Test mutator logic with different values.
137 | $this->assertEquals(2, $m->setStatusAttribute('disabled');
138 | $this->assertEquals(3, $m->setStatusAttribute('pending');
139 |
140 | // Abnormal case
141 | $this->expectException(\InvalidArgumentException::class);
142 | $m->setStatusAttribute('invalid_status');
143 | }
144 | ```
145 |
146 | ### Events
147 |
148 | Model includes protected static `$dispatcher` property, which stores application event handler. If your model
149 | uses internal events or observers, make sure that proper listeners are assigned by mocking dispatcher class.
150 |
151 | ```php
152 | public function test_listeners_attached()
153 | {
154 | $d = m::mock(\Illuminate\Contracts\Events\Dispatcher::class);
155 | User::setEventDispatcher($d);
156 | $name = User::class;
157 |
158 | // Assert that created event has been assigned. Include additional checks if needed.
159 | $d->shouldReceive('listen')->once()->with("eloquent.created: {$name}", m::any());
160 |
161 | User::boot();
162 | }
163 | ```
164 |
165 | > Do not use `Dispather::hasListeners()` method, because events might be assigned from anywhere in the application.
166 | > Purpose of this test is to make sure that events have been assigned inside the model.
167 |
168 | Testing of observers should follow the same approach as for testing any usual classes.
169 | While event handlers, attached during boot can be accessed via `getListeners()` method of event dispatcher.
170 | However it is highly recommended not to use such assignments and use observers instead.
171 |
172 | ### Additional checks.
173 |
174 | All additional methods and configurations must be tested if present, including but not limited to:
175 |
176 | - Custom model methods.
177 | - Usage of traits.
178 | - Custom `CREATED_AT` and `UPDATED_AT` keys.
179 | - `$with` property if set.
180 | - `$incrementing` property if changed by default.
181 | - `$dateFormat` property if changed by default.
182 |
183 | ## Examples
184 |
185 | * [Example abstract model test helper](https://github.com/framgia/laravel-test-examples/blob/master/tests/ModelTestCase.php)
186 | * [Example model](https://github.com/framgia/laravel-test-examples/blob/master/app/City.php) and [test](https://github.com/framgia/laravel-test-examples/blob/master/tests/Unit/CityTest.php)
187 |
--------------------------------------------------------------------------------
/en/Unit/Transformers.md:
--------------------------------------------------------------------------------
1 | # Testing Transformers and Presenters
2 |
3 | Almost every project includes serializations and presenters in one or another way.
4 |
5 | The default way for Laravel is to use mutators/getters on model classes, however in more complex applications,
6 | a variety of ways may be applied.
7 |
8 | This guide is applicable for general cases of transformers or presenters.
9 |
10 | ## Guide
11 |
12 | Having appropriate transformers is critical for API providing applications and highly recommended for others.
13 |
14 | Transformer tests must complete following purposes:
15 |
16 | 1. Ensure data consistency (object structure must not change between commit unexpected).
17 | 2. Ensure property mutations are done correctly.
18 | 3. Ensure data types match expected output.
19 |
20 | For many API client applications appearance of new properties or absence of some can be critical.
21 | It means that any change to such structure must be under control.
22 |
23 | Ensure that your transformers always have consistent data
24 |
25 | ```php
26 | public function test_output_contains_valid_structure()
27 | {
28 | $u = new User([
29 | 'name' => 'Jonh',
30 | 'email' => 'jonh@example.com',
31 | 'password' => '123456',
32 | 'created_at' => Carbon::now(),
33 | 'updated_at' => Carbon::now(),
34 | ])
35 |
36 | $u->setRelation('posts', [
37 | new Post([
38 | 'title' => 'Test post',
39 | 'created_at' => Carbon::now(),
40 | 'updated_at' => Carbon::now(),
41 | ])
42 | ])
43 |
44 | $t = (new UserTransformer())->transform($user);
45 |
46 | $this->assertEquals([
47 | 'name', 'email', 'created_at', 'updated_at', 'posts'
48 | ], array_keys($t));
49 |
50 | $this->assertTrue(is_array($t['posts']));
51 |
52 | // Additional checks.
53 | }
54 | ```
55 |
56 | If your application implements feature tests for API, it is suitable to replace this check with `assertJsonStructure`
57 | method in feature test case.
58 |
59 | For JSON outputs it is necessary to check data types, espcecially if your API clients might be written in strict type
60 | languages.
61 |
62 | ```php
63 | public function test_data_types()
64 | {
65 | $u = new User([
66 | 'id' => '1',
67 | 'name' => 'Jonh',
68 | 'email' => 'jonh@example.com',
69 | 'created_at' => Carbon::now(),
70 | ])
71 |
72 | $t = (new UserTransformer())->transform($user);
73 |
74 | $this->assertInternalType('int', $t['id']);
75 | $this->assertInternalType('string', $t['name']);
76 | $this->assertInternalType('string', $t['email']);
77 | $this->assertInternalType('array', $t['created_at']);
78 | $this->assertInternalType('string', $t['created_at']['date']);
79 | $this->assertInternalType('int', $t['created_at']['timezone_type']);
80 | $this->assertInternalType('string', $t['created_at']['timezone']);
81 | }
82 | ```
83 |
--------------------------------------------------------------------------------
/en/Unit/Validation.md:
--------------------------------------------------------------------------------
1 | # Testing Validation Rules.
2 |
3 | There are 2 approaches for testing validation rules:
4 | 1. Check if necessary rules has been set
5 | 2. Check if invalid data cannot pass validation
6 |
7 | While first is very simple and does not require any complicated test cases, the second might need a lot of data
8 | to test. It is highly recommended to provide new test cases for every appearance of invalid data during user testing
9 | or production use.
10 |
11 | ## Guide
12 |
13 | ### Testing validation rules
14 |
15 | Validation rules are set in 2 common ways: various use inside app logic (via controller `validate()` method, `Validator`
16 | facade, etc.) or via form requests.
17 |
18 | First case must be tested via mocking validator instance and running test on the class which contains logic.
19 |
20 | ```php
21 | public function test_store_method()
22 | {
23 | $c = new Controller();
24 | $v = m::mock(\Illuminate\Contracts\Validation\Factory::class);
25 |
26 | // Substitute Validator instance in DI container.
27 | $previous = $this->app->make(\Illuminate\Contracts\Validation\Factory::class);
28 | $this->app->bind(\Illuminate\Contracts\Validation\Factory::class, $v);
29 |
30 | $r = new Request();
31 | $request->headers->set('content-type', 'application/json');
32 | $data = [
33 | 'name' => 'Jonh',
34 | 'email' => 'jonh@example.com',
35 | ];
36 | $request->setJson(new ParameterBag($data));
37 |
38 | $v->expects('make')->once()
39 | ->with($data, [
40 | 'name' => 'required',
41 | 'email' => 'required|email',
42 | ], m::any(), m::any())
43 | ->andReturnUsing(function (...$args) { return $previous->make(...$args); });
44 |
45 | $c->store($request);
46 |
47 | // Additional assertions.
48 | }
49 | ```
50 |
51 | In case of form requests it can be done much simpler:
52 |
53 | ```php
54 | public function test_it_contains_valid_rules()
55 | {
56 | $r = new StoreRequest();
57 |
58 | $this->assertEquals([
59 | 'name' => 'required',
60 | 'email' => 'required|email',
61 | ], $r->rules());
62 | }
63 | ```
64 |
65 | ### Testing abnormal data
66 |
67 | It is impossible to predict all cases of abnormal data, however when such cases appear, tests must be extended.
68 |
69 | 1. Abnormal data is detected during application use.
70 | 2. Implement test case for controller or other instance with abnormal data to emulate failure.
71 | 3. Implement fix and provide test case with expected interruption.
72 |
73 | ### Testing custom validation rules
74 |
75 | If your application contains custom validations, you must include test cases for such rules in a separate test case.
76 | These tests are required not to check user input, but to test validation logic itself and be sure that it fails or passes
77 | in predicted cases.
78 |
79 | Unfortunately, there is no available method to extract extensions from validation factory, so protected property should be read.
80 |
81 | ```php
82 | // AppServiceProvider::boot
83 | Validator::extend('foo', function ($attribute, $value, $parameters, $validator) {
84 | return $value == 'foo';
85 | });
86 |
87 | // AppServiceProviderTest
88 | public function test_validator_foo_rule()
89 | {
90 | // Extract extensions from validation factory.
91 | $v = $this->app['validator'];
92 | $r = new ReflectionClass($v);
93 | $p = $r->getProperty('extensions');
94 | $p->setAccessible(true);
95 | $extensions = $p->getValue($v);
96 |
97 | // Check if extension had been registered properly.
98 | $this->assertArrayHasKey('foo', $extensions);
99 | $rule = $extensions['foo'];
100 |
101 | // Test cases for extension.
102 | $this->assertTrue($rule('attr', 'foo'));
103 | $this->assertFalse($rule('attr', 'bar'));
104 | }
105 | ```
106 |
107 | Class-based rules (Laravel 5.5) can be simply tested as regular classes with basic rules for code coverage.
108 |
--------------------------------------------------------------------------------
/vn/Conventions.md:
--------------------------------------------------------------------------------
1 | # Laravel Testing Conventions
2 |
3 | ## Cấu trúc thư mục
4 | - Tất cả Unit Tests PHẢI được đặt trong thư mục `tests/Unit`
5 | - Tất cả Integration Tests PHẢI được đặt trong thư mục `tests/Feature`
6 | - Nội dung bên trong thư mục `Unit` hay `Feature` PHẢI có cấu trúc giống với cấu trúc bên trong thư mục `app`. Ví dụ như Unit Test cho file `app/Models/User.php` PHẢI được viết bên trong file `tests/Unit/Models/UserTest.php`
7 |
8 | ## Quy tắc đặt tên
9 | - Toàn bộ test file PHẢI có namespace riêng. Namespace PHẢI được bắt đầu với `Tests\` (hoặc `ASampleProjectTests\`), và tiếp đó là cấu trúc thư mục. Ví dụ, namespace cho file `tests\Unit\Models\UserTest.php` PHẢI là `Tests\Unit\Models\UserTest` (hoặc `ASampleProjectTests\Unit\Models\UserTest`)
10 | - Để đảm bảo tính dễ đọc, nên sử dụng `snake_case` khi viết tên cho các hàm test. Do một hàm test phải bắt đầu bằng `test`, thế nên sau đây là một ví dụ về tên hàm:
11 | ```
12 | public function test_it_throws_an_exception_when_email_is_too_long()
13 | {
14 | }
15 | ```
16 | - Những hàm không làm nhiệm vụ test có thể tuân theo quy tắc đặt tên của PSR-2, tức dùng `camelCase` để đặt tên.
17 |
18 | ## Những thành phần cần viết Unit Test
19 | - **Controllers**: với events handling được disable. Toàn bộ các thành phần bên ngoài PHẢI được mock.
20 | - **Requests** (nếu có): Kiểm tra validation
21 | - **Models**: getters, setters, và những chức năng khác
22 | - **Transformers / Presenters** (nếu có): Kiểm tra kết quả output cho những dữ liệu khác nhau
23 | - **Repositories** (nếu có): Kiểm tra từng hàm có tạo ra đúng SQL queries hay không, hay có các lời gọi hàm, đến mocked query builder, đúng hay không
24 | - **Event listeners**
25 | - **Queue jobs**
26 | - **Auth policies**
27 | - Và các Class chuyên biệt khác trong project
28 |
29 | ## Những thành phần yêu cầu Integration Test
30 | - **Routes**: Kiểm tra đầu vào, đầu ra với toàn bộ hệ thống
31 | - **Route authentication**
32 |
33 | ## Code Coverage
34 | - Code Coverage cho toàn bộ project nên đạt trên `80%`
35 |
--------------------------------------------------------------------------------
/vn/Integration/Integration.md:
--------------------------------------------------------------------------------
1 | # Integration Tests
2 |
3 | Mục đích của Integration Test là để chắc chắn rằng tất cả các components làm việc với nhau như ta mong muốn.
4 |
5 | Khi thực hiện kiểu test này, hãy nhớ rằng những thay đổi với data storage hay những thao tác với các service bên ngoài là có thể xảy ra.
6 |
7 | Để có thể giải quyết một cách đúng đắn những tình huống như vậy, có thể bạn vẫn cần phải tạo các [Test Doubles](../Knowledge.md#test-doubles) cho một vài class.
8 |
9 | Việc những Class nào cần đến Test Doubles thì phụ thuộc vào cách tiếp cận của từng team, và có thể khác biệt phụ thuộc vào độ lớn của application.
10 |
11 | ## Guide
12 |
13 | Laravel cung cấp cho chúng ta 2 cách để thực hiện Integration Test: tạo HTTP requests đến routes, và browser-based testing với `laravel\dusk` package.
14 |
15 | Việc viết HTTP test là CẦN THIẾT, trong khi browser test thì không có yêu cầu cụ thể.
16 |
17 | Integration test PHẢI cover được TOÀN BỘ route của application, và phải tuân thủ các mục đích sau:
18 |
19 | **HTTP**
20 | - Authentication và Policy tests. Mỗi test case cần có những test assertion riêng biệt.
21 | - Kiểm tra Status Codes cho từng loại response.
22 | - Kiểm tra Redirect Codes và Paths cho các các sự kiện khác nhau.
23 | - Kiểm tra tính đúng đắn của JSON responses.
24 | - Kiểm tra Error Handlers cho các response.
25 | - Kiểm tra tính đúng đắn của API versions (nếu cần thiết).
26 |
27 | **Database**
28 | - Đảm bảo data được ghi vào database một cách đúng đắn trong quá trình xử lý requests.
29 | - Kiểm tra quá trình migration.
30 | - Kiểm tra việc insert dữ liệu không bình thường thì có được xử lý chính xác hay không.
31 |
32 | Nếu application của bạn có tương tác với những services hay servers bên ngoài, thì bạn cần sử dụng phiên bản Staging của những services đó, hoặc `mock` class Client của bạn.
33 |
34 | Testing environment có thể được định nghĩa bởi những config và biến đặc biệt. Chặng hạn như ở `testing` database connection ta có thể tạo biến môi trường là `DB_TEST_DATABASE`. Tuy nhiên, ta nên làm theo cách là thay đổi giá trị của các biến production ở bên trong file `phpunit.xml` hoặc trong config CI.
35 |
36 | Sẽ là tốt hơn nếu phần tests có thể được chạy hai lần, với `APP_ENV` được đặt giá trị lần lượt là `testing` và `production`.
37 |
38 | ```xml
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | ```
49 |
--------------------------------------------------------------------------------
/vn/Introduction.md:
--------------------------------------------------------------------------------
1 | # Giới thiệu về Laravel Testing
2 |
3 | ## Test Case
4 | - Test Case là một thuật ngữ phổ biến thường dùng trong các bài Test cụ thể. Nó thường là đơn vị nhỏ nhất của Testing.
5 | - Một Test Case sẽ bao gồm các thông tin như requirements testing (các inputs, điều kiện thực thi), test steps, verification steps, prerequisites, outputs, test environment ...
6 |
7 | ## Unit Test
8 | - Unit Testing là một phương pháp kiểm thử phần mềm mà ở đó từng **đơn vị riêng lẻ** (Individual Unit) của source code được test để kiểm tra xem chúng có đảm bảo chất lượng để sử dụng không.
9 | - Ở trong Laravel Project, viết Unit Test là quá trình mà ở đó, tất cả các Test Case cho từng **function/method** riêng biệt được viết.
10 | - Unit Test cần được thiết kế để kiểm tra function một cách **độc lập**. Hay nói cách khác: **`A` cần `B` để chạy. Ngay cả khi `B` có lỗi, thì Unit Test của `A` VẪN SẼ PASS nếu không có vấn đề gì với `A`**
11 | - Một Unit Test tốt **KHÔNG NÊN** thực hiện những việc như sau:
12 | - Trigger để chạy codes từ những functions khác trong project
13 | - Truy vấn vào cơ sở dữ liệu
14 | - Sử dụng file system
15 | - Truy cập mạng
16 | - ...
17 |
18 | ## Integration Test
19 | - Integration Testing là công đoạn trong kiểm thử phần mềm mà ở đó, từng đơn vị phần mềm riêng biệt được kết hợp lại, và kiểm tra như một khối thống nhất.
20 | - Nếu như Unit Test giúp chúng ta chắc chắn rằng từng function riêng biệt hoạt động chính xác, thì Integration Test giúp chúng ta chắc chắn rằng các thành phần khác trong của project sẽ hoạt động hoàn hảo khi chúng kết hợp với nhau trong thực tế.
21 |
22 | ## PHPUnit
23 | - **PHPUnit** là một Testing Framework hướng đến lập trình viên cho PHP. Nó là một phiên bản của kiến trúc xUnit dành cho Unit Testing Frameworks.
24 | - Các bạn có thể tham khảo [hướng dẫn cài đặt](https://phpunit.de/getting-started.html)
25 |
26 | ### Cài đặt theo từng project
27 | ```
28 | $ composer require --dev phpunit/phpunit
29 | ```
30 |
31 | - PHPUnit được tích hợp sẵn trong Laravel Laravel project. Bạn có thể bắt đầu Test app của mình bằng câu lệnh
32 | ```
33 | $ ./vendor/bin/phpunit
34 | ```
35 |
36 | > Cài đặt và sử dụng theo từng Project được khuyến khích sử dụng, bởi bạn có thể sẽ cần những version khác nhau của PHPUnit trong những project khác nhau.
37 |
38 | ### Cài đặt Global
39 |
40 | - Thông qua download:
41 | ```
42 | $ wget https://phar.phpunit.de/phpunit.phar
43 | $ chmod +x phpunit.phar
44 | $ sudo mv phpunit.phar /usr/local/bin/phpunit
45 | ```
46 | - Thông qua composer:
47 | ```
48 | $ composer global require phpunit/phpunit
49 | ```
50 |
51 | Hãy chắc chắn rằng bạn có `/home//.composer/vendor/bin` hoặc `c:\Users\AppData\Roaming\Composer\vendor\bin` được add vào trong biến `PATH` của bạn.
52 |
53 | - Sau đó, bạn sẽ có thể chạy bản PHPUnit đã được cài đặt global ở bất cứ nơi đâu:
54 | ```
55 | $ phpunit
56 | ```
57 |
58 | ## Code Coverage
59 | - **Code coverage** là một phương pháp đánh giá được dùng để mô tả mức độ mà source code của một chương trình đã được thực thi, khi mà một bộ Test cụ thể chạy. Nói một cách khác, **Code coverage** là một cách để đảm bảo rằng Tests của bạn thực sự đang test Codes của bạn!
60 | - Công thức tính Code coverage:
61 | ```
62 | Code Coverage = (Tổng số dòng Code được gọi bởi các bài Tests của bạn) / (Tổng số dòng Code trong thực tế) x 100%
63 | ```
64 |
65 | Ví dụ: Nếu code coverage của bạn là 90%, điều đó có nghĩa là 90% các dòng codes trong project của bạn đã được gọi ghi chạy Test.
66 | - Code Coverage có thể được tạo ra bằng PHPUnit với extension with **Xdebug** được kích hoạt. Bởi vậy, hãy chắc chắn rằng bạn đã cài đặt và bật Xdebug lên. Tham khảo [Xdebug Installation Guide](https://xdebug.org/docs/install)
67 | - Bạn có thể chạy PHPUnit để tạo coverage report ở định đạng HTML theo câu lệnh sau:
68 | ```
69 | phpunit --coverage-html
70 |
71 | // Hoặc tạo report với định dạng Clover XML format.
72 | phpunit --coverage-clover
73 | ```
74 | - Code Coverage thực sự là một công cụ rất hữu ích để tìm kiếm những thành phần chưa được tests trong project của bạn. Tuy nhiên, nó không phải là một con số vạn năng để có thể đảm bảo cho chất lượng Tests của bạn.
75 |
76 | ## Tài liệu tham khảo thêm
77 | - [PHPUnit Manual](https://phpunit.de/manual/current/en/phpunit-book.pdf)
78 | - [Laravel Testing Official Documents](https://laravel.com/docs/master/testing)
79 | - [Laravel Testing Decoded](https://leanpub.com/laravel-testing-decoded)
80 | - Laracast's [Testing Jargon](https://laracasts.com/series/testing-jargon)
81 | - Laracast's [Testing Laravel](https://laracasts.com/series/phpunit-testing-in-laravel)
82 | - Laracast's [Intuitive Integration Testing](https://laracasts.com/series/intuitive-integration-testing)
83 |
--------------------------------------------------------------------------------
/vn/Knowledge.md:
--------------------------------------------------------------------------------
1 | # Knowledge about Unit Testing
2 |
3 | ## Classify
4 |
5 | Trước khi tạo bất cứ Test Cases nào, chúng ta nên xác định rõ giá trị đầu vào của **từng function/method** cần được test. Các Test Cases phải được thiết kế để có thể cover được hết các sự kết hợp của các giá trị inputs cùng các điều kiện.
6 |
7 | Nhìn chung, chúng ta thường chia test case ra làm 3 loại dựa trên dữ liệu inputs cho Unit Test.
8 |
9 | - **Normal**: Inputs thuộc vào dải dữ liệu bình thường (accepted). Một lượng lớn codes có thể được cover bằng cách chỉ cần chạy **normal** test cases.
10 | - **Boundary**: Inputs bằng hoặc xấp xỉ giá trị maximum hay minimum. Chúng được sử dụng để phát hiện lỗi tại cận, thay vì tìm kiếm lỗi tại những vị trí ở giữa trong dải input.
11 | - **Abnormal**: Inputs là không hợp lệ hay không được kỳ vọng, dùng để kiểm tra khả năng handle lỗi.
12 |
13 | Ví dụ: *Giả sử như chúng ta có một function để kiểm tra địa chỉ email nhập vào từ user. Độ dài tối đa của email là 50 ký tự.*
14 |
15 | ```php
16 | function validate($email) {
17 | if (filter_var($email, FILTER_VALIDATE_EMAIL) && strlen($email) <= 50) {
18 | return true;
19 | }
20 | return false;
21 | }
22 |
23 | ```
24 |
25 | Chúng ta nên viết các Test Cases như sau:
26 |
27 |
28 | Normal cases
29 |
30 | ```php
31 | public function test_valid_email_format_and_length()
32 | {
33 | // Email with length 18 (less than: maximum - 1)
34 | $email = 'sample@framgia.com';
35 | $this->assertEquals(true, validate($email));
36 | }
37 | ```
38 |
39 |
40 |
41 |
42 | Boundary cases
43 |
44 | ```php
45 | public function test_valid_email_format_and_length_max_minus()
46 | {
47 | // Email with length 49 (maximum - 1)
48 | $email = 'samplesamplesamplesamplesamplesamples@framgia.com';
49 | $this->assertEquals(true, validate($email));
50 | }
51 |
52 | public function test_valid_email_format_and_length_max()
53 | {
54 | // Email with length 50 (equal maximum)
55 | $email = 'samplesamplesamplesamplesamplesamplesa@framgia.com';
56 | $this->assertEquals(true, validate($email));
57 | }
58 |
59 | public function test_valid_email_format_and_length_max_plus()
60 | {
61 | // Email with length 51 (maximum + 1)
62 | $email = 'samplesamplesamplesamplesamplesamplesam@framgia.com';
63 | $this->assertEquals(false, validate($email));
64 | }
65 | ```
66 |
67 |
68 |
69 |
70 | Abnormal cases
71 |
72 | ```php
73 | public function test_invalid_email_format()
74 | {
75 | // Invalid email format with normal length (between 0 ~ 50)
76 | $email = 'framgia.com';
77 | $this->assertEquals(false, validate($email));
78 | }
79 |
80 | public function test_valid_email_format_and_length_exceeded()
81 | {
82 | // Email with length 54
83 | $email = 'samplesamplesamplesamplesamplesamplesample@framgia.com';
84 | $this->assertEquals(false, validate($email));
85 | }
86 | ```
87 |
88 |
89 |
90 | ## Test Doubles
91 | Một trong những yêu cầu cơ bản của Unit Test đó là tính **cô lập** (**isolation**). Nhìn chung thì tính cô lập là rất khó (nếu không muốn nói là không thể) bởi luôn luôn có rất nhiều dependencies trong cả project.
92 |
93 | Vì thế, khái niệm về `Test Doubles` ra đời. Một `Test Double` cho phép chúng ta loại bỏ dependency nguyên bản, từ đó giúp cô lập unit.
94 |
95 | Dưới đây là một vài loại `Test Doubles`
96 |
97 | ***Một vài phần trong các định nghĩa sau được lấy từ bài viết [Mocks Aren't Stubs](https://martinfowler.com/articles/mocksArentStubs.html) trên blog của Martin Fowler***
98 |
99 | ### Dummies
100 | - Dummy là objects được truyền vào nhưng mà không hề được sử dụng. Chúng thường chỉ được dùng để hoàn thành danh sách parameter.
101 |
102 | ### Fake
103 | - Fake objects thực ra có mang những triển khai logic, thế nhưng thường sử dụng những "lối tắt", khiến chúng không thích hợp để triển khai trên production (Ví dụ như in memory database)
104 |
105 | ### Stubs
106 | - Stubs đưa ra những câu trả lời có sẵn cho các lời gọi hàm được thực hiện trong quá trình test, và thường sẽ không trả về bất cứ cái gì ngoài những thứ mà chúng đã được lập trình trong bài test.
107 |
108 | ### Mocks
109 | - Mocks là objects đã được lập trình trước với các expectations, tạo ra một đặc tả cho lời gọi mà chúng dự kiến sẽ nhận được.
110 |
111 | ### Stubs vs Mocks
112 | - Stub giúp chúng ta chạy test. Mock là object thực hiện test.
113 | - Một Fake mà bạn dùng để kiểm tra lời gọi hàm thì là Mock. Nếu không nó là Stub.
114 | - Stub không bao giờ có thể làm cho test fail. Mock thì có thể.
115 |
116 | ## Examples
117 | - Dưới đây là các PHP Mocking Frameworks mà bạn có thể sử dụng để dễ dàng tạo ra Mocks phục vụ việc test:
118 | - Mockery: Được khuyến khích sử dụng. Đã được tích hợp ngay trong Laravel Project. Tham khảo tài liệu ở [đây](http://docs.mockery.io/)
119 | - Prophecy: Một phần của PHPSpec project, nhưng có thể được sử dụng độc lập bên ngoài PHPSpec. Xem thêm tại [đây](https://github.com/phpspec/prophecy)
120 | - Ví dụ về việc tạo Stubs và Mocks với **Mockery**
121 |
122 | // TODO
123 |
--------------------------------------------------------------------------------
/vn/Unit/Common.md:
--------------------------------------------------------------------------------
1 | # Common Testing Rules
2 |
3 | Với những thành phần như Events, Event Listeners, Subscribers, Queue Jobs và Validation Rule Classes, bạn nên tuân theo một số quy tắc cơ bản sau:
4 |
5 | 1. Test `__construct()` method để chắc chắn rằng các properties đều được assigned.
6 | 2. Cover tất cả các public methods với những tests cần thiết.
7 | 3. Thật chú ý vào **main method** của class (e. g. `handle` đối với Listeners hoặc `passes` với rules).
8 | 4. Coverage là không cần thiết đối với những methods mà trả về text (ví dụ như `message` đối với rules).
9 | 5. Coverage là không bắt buộc đối với những hàm get/set cơ bản, hàm mà không chỉnh sửa tham số input.
10 |
11 | ## Mocking DI instances
12 |
13 | Laravel sử dụng injection trong rất nhiều trường hợp và components của bạn có thể sử dụng nó thông qua method/constructor injection hoặc trực tiếp trong methods thông qua container instance hoặc Facades.
14 |
15 | Trong những trường hợp như vậy, sẽ là rất quan trọng để tạo Test Doubles cho từng injected instance và chắc chắn rằng không có instances nào khác được resolved.
16 |
17 | Mặc định thì `TestCase` class được cung cấp với một application instance được khởi tạo sẵn bởi Laravel. Mọi thứ là tốt nếu bạn sử dụng cách tiếp cận này, tuy nhiên nên có thêm một class test case cơ bản với instance application được mock.
18 |
19 | ```php
20 | class MockApplicationTestCase extends TestCase
21 | {
22 | /**
23 | * Creates the application.
24 | *
25 | * @return \Illuminate\Foundation\Application|\Mockery\Mock
26 | */
27 | public function createApplication()
28 | {
29 | return m::mock(\Illuminate\Foundation\Application::class)->makePartial();
30 | }
31 | }
32 | ```
33 |
34 | Cách tiếp cận này sẽ giúp bạn kiểm soát tất cả các quá trình Dependecies Injection một cách cẩn thận. Và giờ bạn có thể inject Test Doubles một cách đơn giản.
35 |
36 | ```php
37 | public function test_it_assignes_event_handler()
38 | {
39 | $e = m::mock(Dispatcher::class);
40 | $this->app->bind(Dispatcher::class, $e);
41 | // Test event assignment with mocked class.
42 | }
43 | ```
44 |
45 | ## Testing Database Queries
46 |
47 | Tính cô lập là rất quan trọng cho test cases, và việc chắc chắn rằng SQL queries được sinh ra chính xác là cực kỳ cần thiết.
48 |
49 | Tất cả các thao tác với Database trong Laravel đều được thực hiện thông qua class `Illuminate\Database\Connection`, và các methods của nó thì dễ dàng được mock bằng cách sử dụng Mockery package.
50 |
51 | ```php
52 | $connection = m::mock(Connection::class);
53 | $query = new Builder($connection, new SQLiteGrammar(), new Processor());
54 | // DB::table is the most common call and it is simpler to mock the method manually instead of dealing with partial mocks.
55 | $connection->allows()->table()->andReturnUsing(function ($table) use ($query) {
56 | return $query->from($table);
57 | })
58 |
59 | // Test your queries
60 |
61 | $connection->shouldReceive('select')->with(
62 | 'select * from "streets" limit 10 offset 0', // query
63 | [], // bindings
64 | m::any() // useReadPdo. use true/false if necessary
65 | )->andReturn([
66 | // 'query result'
67 | ]);
68 |
69 | $someClass->methodUsingConnection();
70 | ```
71 |
72 | Đôi khi, sẽ là rất tiện lợi khi ta inject một Connection đã được mock vào trong DI để có thể sử dụng nó ở mọi nơi.
73 | Ở đây ta trực tiếp thay đổi property, bởi sự mở rộng mặc định thì yêu cầu mock connection methods, thứ mà chúng ta không cần.
74 |
75 | ```php
76 | public function setUp()
77 | {
78 | $connection = m::mock(Connection::class);
79 | // Replace SQLiteGrammar and Processor if necessary.
80 | $query = new Builder($connection, new SQLiteGrammar(), new Processor());
81 | $connection->allows()->table()->andReturnUsing(function ($table) use ($query) {
82 | return $query->from($table);
83 | })
84 |
85 | // Replace current default connection (if necessary)
86 | $this->afterApplicationCreated(function () use ($connection) {
87 | $manager = $app['db'];
88 | $name = $manager->getDefaultConnection();
89 | $manager->purge($name);
90 |
91 | $r = new ReflectionClass($manager);
92 | $p = $r->getProperty('connections');
93 | $p->setAccessible(true);
94 | $p->setValue($manager, [
95 | $name => $connection,
96 | ])
97 | });
98 |
99 | // Assign a separate "mock" connection
100 | $this->afterApplicationCreated(function () use ($connection) {
101 | $manager = $app['db'];
102 | $manager->setDefaultConnection('mock');
103 |
104 | $r = new ReflectionClass($manager);
105 | $p = $r->getProperty('connections');
106 | $p->setAccessible(true);
107 | $list = $p->getValue($manager);
108 | $list['mock'] = $connection;
109 | $p->setValue($manager, $list);
110 | });
111 |
112 |
113 | parent::setUp();
114 | }
115 | ```
116 |
117 | Bạn cần phải nhớ tuân theo những thứ sau khi thực hiện database testing
118 | - Bạn có thể có connection có tên (named connection), hãy cẩn thận với nó.
119 | - Models lấy ra connections thông qua `ConnectionResolverInterface`, thứ được tự gán vào trong model và có thể khác biệt.
120 | - Queries được tạo ra khác nhau với những cú pháp khác nhau (db khác nhau). Hãy sử dụng cái thích hợp cho project của bạn.
121 |
122 |
--------------------------------------------------------------------------------
/vn/Unit/Controllers.md:
--------------------------------------------------------------------------------
1 | # Testing Controllers
2 |
3 | Controllers chứa những giải thuật chính cho vấn đề xử lý request. Chúng cần phải được chú ý nhiều trong quá trình testing.
4 |
5 | Unit Tests viết cho controllers phải được cô lập hoàn toàn, và bất cứ lời gọi ra bên ngoài nào từ bên trong controller method phải được mock.
6 |
7 | Tuỳ vào logic trong application của bạn, mà controllers có thể thực hiện việc Validation ở phần logic bên trong method hay thông qua Form Request.
8 |
9 | Nếu là trường hợp trước, Controller Tests PHẢI bao gồm các cases liên quan đến **[boundary](../Knowledge.md#Classify)** và **[abnormal](../Knowledge.md#Classify)** request input. Ở trường hợp còn lại, những cases trên phải được test trong Form Request test cases tương ứng.
10 |
11 | Tuy nhiên, điều này chỉ áp dụng với input được thực hiện bởi user. Data được cung cấp bởi những hàm bên ngoài controller method phải được mock chính xác với tất cả các loại data.
12 |
13 | ## Guide
14 |
15 | ## Examples
16 |
17 | * [Resource web controller](https://github.com/framgia/laravel-test-examples/app/Http/Controllers/CityController.php) and [tests](https://github.com/framgia/laravel-test-examples/blob/master/tests/Unit/Http/Controllers/CityControllerTest.php)
18 |
--------------------------------------------------------------------------------
/vn/Unit/Middleware.md:
--------------------------------------------------------------------------------
1 | # Testing Middleware Classes
2 |
3 | Middleware tests cases nhìn chung không có gì khác biệt nhiều với hầu hết các class khác. Ngoại trừ việc bạn cần chú ý đến `$next` callback.
4 |
5 | ## Guide
6 |
7 | Bạn nên nhớ rằng middleware có thể trả về data mà không cần gọi `$next`, và đó có thể là trường hợp không mong muốn.
8 | Ta có các trường hợp xử lý với middleware như sau:
9 |
10 | - Trả về kết quả của `$next` closure: basic *before* middleware
11 | - Trả về response mà không gọi `$next`: abortion middleware.
12 | - Nhận về response từ `$next` và thực hiện thay đổi trên đó: basic *after* middleware.
13 |
14 | Dựa trên cách thực hiện của bạn mà những cases trên có thể được dùng cùng nhau. (ví dụ như conditional abortion hoặc before/after execution).
15 |
16 | Để mock `$next` callback, bạn có thể đơn giản là tạo một closure với các assertions ở bên trong:
17 |
18 | ```php
19 | public function test_middleware_appends_header()
20 | {
21 | //...
22 | $next = function ($request) {
23 | $this->assertEquals('Appended_header_data', $request->headers->get('header-name'));
24 | return response('ok');
25 | }
26 |
27 | $middleware->handle($request, $next);
28 | //...
29 | }
30 | ```
31 |
32 | Đôi khi middleware cần tương tác với Session, Cache hoặc các components khác. Trong trường hợp đó, bạn cần mock những components này một cách chính xác.
33 |
34 | Trong trường hợp đặc biệt cần đến chỉnh sửa session, bạn nên sử dụng `$request->getSession()` ở bên trong method, thay vì dùng Facades hoặc Dependecy Injection.
35 |
36 | ## Examples
37 |
38 | * [Middlewares](https://github.com/framgia/laravel-test-examples/tree/master/app/Http/Middleware)
39 | * [Middleware tests](https://github.com/framgia/laravel-test-examples/tree/master/tests/Unit/Http/Middleware)
--------------------------------------------------------------------------------
/vn/Unit/Models.md:
--------------------------------------------------------------------------------
1 | # Testing Models
2 |
3 | Models là một trong những class được sử dụng nhiều nhất trong Laravel Application. Cùng với đó, chúng là thành phần phức tạp nhất trong số các default components.
4 |
5 | Tuỳ vào cách bạn thực hiện, mà models có thể bao gồm validation, external services connection (e. g. ElasticSearch indexing), hay property getters và nhiều custom methods khác.
6 |
7 | Do models được sử dụng ở khắp nơi trong application, nên chúng phải được test một cách cẩn thận.
8 |
9 | ## Guide
10 |
11 | ### Configuration properties
12 |
13 | Mọi Model đều bao gồm một vài các thiết lập cơ bản với các public hoặc protected properties. Những properties này có thể ảnh hưởng rất nhiều đến cách thức hoạt động của application, tuy nhiên chúng là là những thành phần dễ test nhất.
14 |
15 | Bạn có thể assert giá trị trả về của property getters:
16 |
17 | ```php
18 | public function test_contains_valid_fillable_properties()
19 | {
20 | $m = new User();
21 | $this->assertEquals(['name', 'email'], $m->getFillable());
22 | }
23 | ```
24 |
25 | Những method tests sau cần phải được sử dụng trong quá trình tests:
26 |
27 | - `$fillable` -> `getFillable()`
28 | - `$guarded` -> `getGuarded()`
29 | - `$table` -> `getTable()`
30 | - `$primaryKey` -> `getKeyName()`
31 | - `$connection` -> `getConnectionName()`: Trong trường hợp bạn sử dụng multiple connections.
32 | - `$hidden` -> `getHidden()`
33 | - `$visible` -> `getVisible()`
34 | - `$casts` -> `getCasts()`: note that method appends incrementing key.
35 | - `$dates` -> `getDates()`: note that method appends `[static::CREATED_AT, static::UPDATED_AT]`.
36 | - `newCollection()`: kiểm tra collection có đúng kiểu dữ liệu hay không. Sử dụng `assertEquals` với kết quả trả về từ hàm `get_class()`, chứ không dùng `assertInstanceOf`.
37 |
38 | Chúng ta có thể đặt hết tất cả những kiểm tra trên bên trong một test method, chẳng hạn như `test_model_configuration()`
39 |
40 | ### Relations
41 |
42 | Relations được định nghĩa là các methods trả về class relation tương ứng. Trong nhiều trường hợp, relation có thể được định nghĩa với custom foreign keys, hoặc với những câu query thêm vào. Cách tốt nhất để test relation config là assert instance `Relation` được trả về.
43 |
44 | Trong tất cả các trường hợp bình thường, các relations được tạo ra với `$model->newQuery()` như là builder instance. Nếu không được chỉnh sửa gì, nó có thể được kiểm tra một cách đơn giản với `assertEquals`
45 |
46 | ```php
47 | public function test_user_relation()
48 | {
49 | $m = new Post();
50 | $relation = $m->user();
51 |
52 | $this->assertInstanceOf(BelongsTo::class, $relation);
53 | $this->assertEquals('user_id', $relation->getForeignKey());
54 | $this->assertEquals('id', $relation->getOwnerKey());
55 | //...
56 | $this->assertEquals($m->newQuery(), $relation->getQuery());
57 | }
58 | ```
59 |
60 | Tuỳ theo loại quan hệ, mà các test assertion sau cần phải được sử dụng:
61 |
62 | - `getQuery()`: kiểm tra query chưa được chỉnh sửa, hoặc đã được chỉnh sửa một cách chính xác.
63 | - `getForeignKey()`: với những quan hệ `HasOneOrMany` hoặc `BelongsTo`, nhưng loại key thì khác nhau (see documentaiton).
64 | - `getQualifiedParentKeyName()`: với quan hệ `HasOneOrMany`, do không có `getLocalKey()` method, nên method này cần được kiểm tra.
65 | - `getOwnerKey()`: với quan hệ `BelongsTo` và những quan hệ mở rộng từ nó.
66 | - `getQualifiedForeignPivotKeyName()`: với quan hệ `BelongsToMany`.
67 | - `getQualifiedRelatedPivotKeyName()`: với quan hệ `BelongsToMany`.
68 | - `getTable()`: với quan hệ `BelongsToMany`. Không bắt buộc, bởi nó được bao gồm trong những method ở trên.
69 | - `getMorphType()`: với quan hệ polymorphic.
70 |
71 | ### Property values
72 |
73 | Rất nhiều models có thể bao gồm property mutators hay getters. Những methods này thực hiện tay đổi output trong rất nhiều trường hợp, bao gồm cả kết quả từ `__get` và `toArray()`.
74 |
75 | Cần phải có những test cases thực hiện assertion trên tất cả các properties.
76 |
77 | Trong trường hợp này, bạn cần sử dụng `unguarded` helper hoặc tự loại bỏ guards.
78 |
79 | ```php
80 | public function test_properties_have_valid_values()
81 | {
82 | User::unguard();
83 |
84 | $initial = [
85 | 'name' => 'Jonh Doe',
86 | 'email' => 'jonh@example.com',
87 | ];
88 |
89 | $m = new User($initial);
90 |
91 | $this->assertEquals($initial, $m->setHidden([]), $m->attributesToArray());
92 | }
93 | ```
94 |
95 | Trong trường hợp có những getter, thì giá trị trả ra có thể bị thay đổi, bởi vậy tests cần cover các trường hợp này.
96 |
97 | Sử dụng `getAttributeValue()` để chắc chắn rằng getter đã được gọi. Sau đó gọi đến getter để test giá trị trả ra là khác nhau.
98 |
99 | Trong trường hợp có cả mutators, sử dụng `setRawAttributes` để set giá trị ban đầu mà không gặp phải những sự thay đổi không lường trước được trên giá trị đó.
100 |
101 | ```php
102 | public function test_status_getter()
103 | {
104 | $m = new User();
105 | $m->setRawAttributes([
106 | 'status' => 1,
107 | ]);
108 |
109 | // Test if getter is working.
110 | $this->assertEquals('enabled', $m->getAttributeValue('status'));
111 |
112 | // Test getter logic with different values.
113 | $this->assertEquals('disabled', $m->getStatusAttribute(2));
114 | $this->assertEquals('pending', $m->getStatusAttribute(3));
115 |
116 | // Abnormal case
117 | $this->expectException(\InvalidArgumentException::class);
118 | $m->getStatusAttribute(4);
119 | }
120 | ```
121 |
122 | Những quy tắc như vậy cũng được apply cho Mutators. Để tránh các getters được thực hiện, sử dụng method `getAttributes()` cho các assertions.
123 |
124 | ```php
125 | public function test_status_getter()
126 | {
127 | $m = new User();
128 |
129 | // Test if mutator is working.
130 | $m->setAttribute('status', 'enabled');
131 | $this->assertEquals(1, $m->getAttributes()['status']);
132 |
133 | // Test mutator logic with different values.
134 | $this->assertEquals(2, $m->setStatusAttribute('disabled');
135 | $this->assertEquals(3, $m->setStatusAttribute('pending');
136 |
137 | // Abnormal case
138 | $this->expectException(\InvalidArgumentException::class);
139 | $m->setStatusAttribute('invalid_status');
140 | }
141 | ```
142 |
143 | ### Events
144 |
145 | Model bao gồm protected static `$dispatcher` property, thứ có chứa application event handler. Nếu model của bạn sử dụng internal events hay observers, hãy chắc chắn rằng listeners tương ứng được assign thông qua class dispather được mock.
146 |
147 | ```php
148 | public function test_listeners_attached()
149 | {
150 | $d = m::mock(\Illuminate\Contracts\Events\Dispatcher::class);
151 | User::setEventDispatcher($d);
152 | $name = User::class;
153 |
154 | // Assert that created event has been assigned. Include additional checks if needed.
155 | $d->shouldReceive('listen')->once()->with("eloquent.created: {$name}", m::any());
156 |
157 | User::boot();
158 | }
159 | ```
160 |
161 | > Không sử dụng `Dispather::hasListeners()` method, bởi event có thể được assign từ bất kỳ đâu trong application.
162 | > Mục đích của test này là để đảm bảo rằng events được assign từ bên trong Model.
163 |
164 | Testing với observers nên tuân theo những cách thức tiếp cận chung giống với testing cho các class thông thường khác.
165 |
166 | Event handlers được attach trong quá trình boot có thể được truy cập thông qua `getListeners()` method của event dispatcher.
167 | Tuy nhiên hoàn toàn không nên sử dụng assignments kiểu như vậy, mà thay vào đó nên dùng observers.
168 |
169 | ### Additional checks.
170 |
171 | Tất cả các method hay các configuration khác đều phải được test. Chúng bao gồm (không phải là toàn bộ):
172 |
173 | - Custom model methods.
174 | - Sử dụng Traits.
175 | - Custom `CREATED_AT` và `UPDATED_AT` keys.
176 | - `$with` property nếu có.
177 | - `$incrementing` property nếu được thay đổi so với mặc định.
178 | - `$dateFormat` property nếu được thay đổi so với mặc định.
179 |
180 | ## Examples
181 |
182 | * [Example abstract model test helper](https://github.com/framgia/laravel-test-examples/blob/master/tests/ModelTestCase.php)
183 | * [Example model](https://github.com/framgia/laravel-test-examples/blob/master/app/City.php) and [test](https://github.com/framgia/laravel-test-examples/blob/master/tests/Unit/CityTest.php)
184 |
--------------------------------------------------------------------------------
/vn/Unit/Transformers.md:
--------------------------------------------------------------------------------
1 | # Testing Transformers and Presenters
2 |
3 | Hầu hết các project đều bao gồm serializations và presenters, theo cách này hay cách khác.
4 |
5 | Cách thức mặc định mà Laravel cung cấp đó là sử dụng mutators/getters trong Model class, tuy nhiên trong những application phức tạp, ta nên sử dụng những cách khác.
6 |
7 | Hướng dẫn dưới đây có thể được áp dụng cho những trường hợp chung dùng transformers hay presenters.
8 |
9 | ## Guide
10 |
11 | Sử dụng transformers một cách thích đáng là rất cần thiết cho những application cung cấp API, và cũng rất khuyến khích sử dụng trong những application khác nữa.
12 |
13 | Transformer test phải tuân thủ các mục tiêu sau:
14 |
15 | 1. Đảm bảo tính nhất quán của dữ liệu (object structure must not change between commit unexpected).
16 | 2. Đảm bảo proterty mutations được thực hiện chính xác.
17 | 3. Đảm bảo kiểu data khớp với đầu ra mong muốn.
18 |
19 | Trong rất nhiều các API client applications, việc xuất hiện properties mới, hay mất một vài properties có thể sẽ dẫn đến hậu quả nghiêm trọng.
20 | Do đó mọi thay đổi đến cấu trúc object phải luôn được kiểm soát.
21 |
22 | Hãy chắc chắn rằng transformers của bạn luôn có dữ liệu nhất quán.
23 |
24 | ```php
25 | public function test_output_contains_valid_structure()
26 | {
27 | $u = new User([
28 | 'name' => 'Jonh',
29 | 'email' => 'jonh@example.com',
30 | 'password' => '123456',
31 | 'created_at' => Carbon::now(),
32 | 'updated_at' => Carbon::now(),
33 | ])
34 |
35 | $u->setRelation('posts', [
36 | new Post([
37 | 'title' => 'Test post',
38 | 'created_at' => Carbon::now(),
39 | 'updated_at' => Carbon::now(),
40 | ])
41 | ])
42 |
43 | $t = (new UserTransformer())->transform($user);
44 |
45 | $this->assertEquals([
46 | 'name', 'email', 'created_at', 'updated_at', 'posts'
47 | ], array_keys($t));
48 |
49 | $this->assertTrue(is_array($t['posts']));
50 |
51 | // Additional checks.
52 | }
53 | ```
54 |
55 | Nếu application của bạn có implement Feature Test cho API, bạn có thể thay việc check này bằng cách sử dụng method `assertJsonStructure` bên trong Feature test case.
56 |
57 | Với JSON output, cần phải check data type, đặc biệt nếu API Clients của bạn được viết bằng những ngôn ngữ dùng strict-type.
58 |
59 | ```php
60 | public function test_data_types()
61 | {
62 | $u = new User([
63 | 'id' => '1',
64 | 'name' => 'Jonh',
65 | 'email' => 'jonh@example.com',
66 | 'created_at' => Carbon::now(),
67 | ])
68 |
69 | $t = (new UserTransformer())->transform($user);
70 |
71 | $this->assertInternalType('int', $t['id']);
72 | $this->assertInternalType('string', $t['name']);
73 | $this->assertInternalType('string', $t['email']);
74 | $this->assertInternalType('array', $t['created_at']);
75 | $this->assertInternalType('string', $t['created_at']['date']);
76 | $this->assertInternalType('int', $t['created_at']['timezone_type']);
77 | $this->assertInternalType('string', $t['created_at']['timezone']);
78 | }
79 | ```
80 |
--------------------------------------------------------------------------------
/vn/Unit/Validation.md:
--------------------------------------------------------------------------------
1 | # Testing Validation Rules.
2 |
3 | Có 2 cách thực hiện việc test với Validation Rules:
4 |
5 | 1. Kiểm tra xem những rules cần thiết đã được set hay chưa
6 | 2. Kiểm tra nhưng data không hợp lệ thì không thể pass qua được validation
7 |
8 | Trường hợp trước thì rất là đơn giản, và không yêu cầu những test cases phức tạp, trường hợp sau thì lại cần rất nhiều data để test. Những test cases mới nên được bổ sung liên tục mỗi khi có sự xuất hiện của dữ liệu không hợp lệ phát sinh trong quá trình user testing hoặc sử dụng trên production.
9 |
10 | ## Guide
11 |
12 | ### Testing validation rules
13 |
14 | Validation rules được set qua 2 cách thông thường: được sử dụng trong app logic (thông qua hàm `validate()` của controller, hay `Validator` facade ...) hoặc qua Form Requests.
15 |
16 | Trường hợp đầu tiên cần phải được test thông qua mocking validator instance, và chạy test trong những class bao gồm logic.
17 |
18 | ```php
19 | public function test_store_method()
20 | {
21 | $c = new Controller();
22 | $v = m::mock(\Illuminate\Contracts\Validation\Factory::class);
23 |
24 | // Substitute Validator instance in DI container.
25 | $previous = $this->app->make(\Illuminate\Contracts\Validation\Factory::class);
26 | $this->app->bind(\Illuminate\Contracts\Validation\Factory::class, $v);
27 |
28 | $r = new Request();
29 | $request->headers->set('content-type', 'application/json');
30 | $data = [
31 | 'name' => 'Jonh',
32 | 'email' => 'jonh@example.com',
33 | ];
34 | $request->setJson(new ParameterBag($data));
35 |
36 | $v->expects('make')->once()
37 | ->with($data, [
38 | 'name' => 'required',
39 | 'email' => 'required|email',
40 | ], m::any(), m::any())
41 | ->andReturnUsing(function (...$args) { return $previous->make(...$args); });
42 |
43 | $c->store($request);
44 |
45 | // Additional assertions.
46 | }
47 | ```
48 |
49 | Trong trường hợp dùng Form Requests, mọi thứ có thể được hoàn thành dễ dàng hơn:
50 |
51 | ```php
52 | public function test_it_contains_valid_rules()
53 | {
54 | $r = new StoreRequest();
55 |
56 | $this->assertEquals([
57 | 'name' => 'required',
58 | 'email' => 'required|email',
59 | ], $r->rules());
60 | }
61 | ```
62 |
63 | ### Testing abnormal data
64 |
65 | Việc đoán trước toàn bộ các abnormal data là hoàn toàn không thể, tuy nhiên, mỗi khi những trường hợp như thế xuất hiện, cần phải bổ sung thêm những test case mới.
66 |
67 | 1. Abnormal data được phát hiện trong quá trình sử dụng application.
68 | 2. Thêm vào những test case cho controller hay các instance khác với abnormal data để mô phỏng lỗi.
69 | 3. Thêm vào bản fix và test case với những lỗi được dự đoán từ trước.
70 |
71 | ### Testing custom validation rules
72 |
73 | Nếu trong application của bạn có những custom validations, bạn cần phải thêm vào những test cases cho các rules đó.
74 |
75 | Những tests này không yêu cầu kiểm tra user input, mà cần phải kiểm tra validation logic để chắc chắn rằng nó fails hoặc passes trong những trường hợp được dự báo trước.
76 |
77 | Thật không may là không có một method nào để extract extensions từ validation factory, bởi thế cần phải đọc ra protected property.
78 |
79 | ```php
80 | // AppServiceProvider::boot
81 | Validator::extend('foo', function ($attribute, $value, $parameters, $validator) {
82 | return $value == 'foo';
83 | });
84 |
85 | // AppServiceProviderTest
86 | public function test_validator_foo_rule()
87 | {
88 | // Extract extensions from validation factory.
89 | $v = $this->app['validator'];
90 | $r = new ReflectionClass($v);
91 | $p = $r->getProperty('extensions');
92 | $p->setAccessible(true);
93 | $extensions = $p->getValue($v);
94 |
95 | // Check if extension had been registered properly.
96 | $this->assertArrayHasKey('foo', $extensions);
97 | $rule = $extensions['foo'];
98 |
99 | // Test cases for extension.
100 | $this->assertTrue($rule('attr', 'foo'));
101 | $this->assertFalse($rule('attr', 'bar'));
102 | }
103 | ```
104 |
105 | Class-based rules (Laravel 5.5) có thể được test một cách đơn giản, giống những class bình thường khác với những rules cơ bản cho code coverage.
106 |
107 |
--------------------------------------------------------------------------------