Lessons Learnt: PHPUnit for Beginners
Published: 2020-07-09 19:40:58

---
title: Lessons Learnt: PHPUnit for Beginners
published: true
date: 2020-06-08 00:00:00 UTC
tags: 
canonical_url: https://martinbetz.eu/articles/lessons-learnt-phpunit-course
---

I took the course [PHPUnit for Beginners by Laravel Daily](https://laraveldaily.teachable.com/p/laravel-phpunit-testing-for-beginners). It is suitable for total testing beginners and walks you through a simple CRUD application.

Here are my takeaways in the format of question and answer. They are sorted by occurence in the course but you can use whatever you need for your application and testing – not everything is related to testing:

## Why choose `@forelse … @empty … @endforelse` for loops?

It covers the case where there is no data

```
@forelse ($users as $user)
     <li>{{ $user->name }}</li>
@empty
    <p>No users</p>
@endforelse
```

## How to create content with custom values with Eloquent

```
$product = Product:create([
  'name' => 'Product 1',
  'price' => 99.99
]);

// in your test
$response->assertSee($product->name);
```

## How to setup test database?

- `phpunit.xml` overwrites `.env.testing`
- Edit `DB_CONNECTION` for `MySQL/sqlite`
- Change value of `DB_DATABASE` into `value=":memory:"` to get fast in memory store

## What does `RefreshDatabase` trait do?

- It Runs migrations
- Creates a fresh database
- Usage
  - `use Illuminate\Foundation\Testing\RefreshDatabase;` above class
  - `use RefreshDatabase;` in class, not single test

## When should you use a MySQL test database?

- When you use raw MySQL statements for the following features:
  - Date formatting
  - String functions
  - Date differences
  - Geospatial features

## How to set up a MySQL test database in `phpunit.xml`?

- `<server name="DB_CONNECTION" value="MySQL"/>`
- `<server name="DB_DATABASE" value="name_of_db"/>`

## Why to test data and not visuals (assertSee)?

- To avoid false positives because of incomplete results
- Example
  - Blade: show product name `{{ $product->name }}`
  - Data: `['name' => 'Product 1000]`
  - Visual test: `$response->assertSee('Product 1')` would turn green and create a false positive

## How to get view data of e.g. $products to test?

```
$view = $response->viewData('products') // was passed to view in controller
$this->assertEquals($product->name, $view->first()->name);
```

## What do unit tests capture?

- Internal logic
- No Endpoint
- Data processing

## How to create a Laravel service to translate currency

- Create service in `app\services` -> `CurrencyService.php`
- Import using `use App\Services\CurrencyService`
- Call `new CurrencyService()->convert();`
- No changes in database needed

## How to create temporary database/accessor field (e.g. dynamic price in another currency)?

- This is also called accessor
- On model `Product.php`

```
public function getPriceEurAttribute() {
    return $this->price*0.8;
}
```

## How to create an unit test?

- `art make:test NAME --unit`

## How to paginate in controller and view?

- (In Controller): `$products = Product::paginate(10);`
- In View: `{{ $products->links() }}`

## How to call factories?

- `factory(Product::class, 10)->create();`

## How to echo variable result into log?

- Call `info($var)` in your code

## How to test if login works?

- Create user
- Post login data and set response

```
$response = $this->post('login', [
  'email' => 'EMAIL',
  'password' => 'PW'
]);
// assert where you expect to be redirected to, e.g. home
$response->assertRedirect('/home');
```

## How to quickly log in for tests?

- `$this->actingAs($user)->get('/')`

## How to protect a route via auth?

- `Route::get('/')->middleware('auth')`

## Easiest way to add admin?

- Add field to user model: `is_admin`
- Add to fillable in model
- Create middleware `app\Http\Middleware\IsAdmin` (see following snippet)
- Add middleware to `App\Kernel`
- Add middleware to your route `Route::middleware('auth', 'is_admin')`

```
public function handle($request, Closure $next) 
{
  if (! auth()->user()->is_admin) 
  {
    abort(403);
  }
  return $next($request);
}
```

## Which visual assertions are usual?

- `$response->assertSee()`
- `$response->assertDontSee()`

## How to create simple factory states?

- Example: `is_admin`, yes/no
- Create private function with factory and optional parameter in it

```
private function create_user(is_admin = 0)
{
  $this->user = factory(User::class)->create([
    'is_admin' => $is_admin,
  ]);
}
```

### How to store everything you get via form?

```
// Controller

public function store(Request $request)
{
    Product::create($request->all());
    return redirect()->route('home');
}
```

## How to test a POST request with parameter `name = 'Test 1'`?

- `$this->post('products', ['name' => 'Test 1', 'price' => 99.99]);`

## How to assert that something is in the database? (db side)

- `$this->assertDatabaseHas('products', ['name' => 'Test 1', 'price' => 99.99]);`

## How to test whether saved data gets returned?

- `$product = Product::orderBy('id', 'desc')->first();`
- `$this->assertEquals('String', $product->name);`
- `$this->assertEquals('price', $product->price);`

## How to check whether data for edit is available in view?

- `$product = Product::first();`
- `$response->assertSee('value="' . $product->price . '"');`

## How to update all data from a request?

```
public function update(Product $product, UpdateProductRequest $request)
{
  $product->update($request->all());
  return redirect()->route('products.index');
}
```

## Where and how to create a form request?

- `app/Http/Requests/UpdateProductRequest.php`

```
public rules() {
  return [
    'name' => 'required',
    'price' => 'required',
  ];
}
```

## How to test an update request?

`$response = $this->put('/products/' . $product->id, ['name' => 'Test']);`

## How to test for session error on 'name'?

`$response->assertSessionHasErrors(['name']);`

## How to update as json API call?

```
$response = $this->actingAs($this->user)
  ->put('/products/' . $product->id,
  [
    'name' => 'Test',
    'price' => 99.99,
  ],
  [
   'Accept' => 'Application/json', 
  ]);
```

## How to create a delete item view?

```
<form action={{ route('products.destroy' . $product->id) }} method="POST" onsubmit="confirm('Sure?')">
<input type="hidden" name="_method" value="DELETE">
<input type="hidden" name="_token" value="{{ csrf_token() }}">
```

## How to delete item in controller?

```
public function destroy(Product $product) {
  $product->delete();
  return redirect()->route('products.index');
}
```

## How to assert that data gets deleted?

1. Create product with factory
2. `$this->assertEquals(1, Product::all())`
3. `$response = $this->actingAs($this->user)->delete('products/' . $product->id);` (Route missing?)
4. `$response->assertStatus(302)`
5. `$this->assertEquals(0, Product::count());`