Part 2: Testing Model Relationships in Laravel — BASIC
Tags: laravel, unittesting, php, codequality
Published: 2019-10-08 11:51:43

---
title: Part 2: Testing Model Relationships in Laravel — BASIC
published: true
tags: laravel,unit-testing,php,code-quality
canonical_url: 
---

### Part 2: Testing Model Relationships in Laravel — BASIC

![](https://cdn-images-1.medium.com/max/1024/1*hEH7jGETW9HaKrcKweyoRw.png)<figcaption>Some Laravel model relationship unit tests</figcaption>

100% code coverage is hardly achievable in code testing, we do aim closer but some tests are really boring. In this multipart post **I helped you edge closer to 100% code coverage by writing some 5%** of the tests you need in all your Laravel apps — the boring and often overlooked tests.   
Eloquent relationships are ever present in every Laravel app but how do you ensure the expected models are all connected? **_This could be your cheatsheet for eloquent relationship tests_**. We are going to write tests that ensure all our models have relevant model connections and that they are correctly set.

- Part 1: [**Intro and Schema Tests**](https://dev.to/tonyfrenzy/part-1-testing-model-relationships-in-laravel-schema-tests-37nj)
- Part 2: **Testing Basic Model Relationships** ⏯
- Part 3: [**Testing Polymorphic Relationships**](https://dev.to/tonyfrenzy/part-3-testing-model-relationships-in-laravel-polymorphic-m51)

---

### THE RELATIONSHIPS

The word **relationship** is expected to involve at least two entities, thus every eloquent relationship has two parties, (a). **_The first_** relationship and (b). **_The Inverse_** relationship (either of the models could be made the _first_ but a _more influential_ or _parent-esque_ model is okay as the first). As we write model relationship tests it is only right to test from the two concerned ends, thus we shall be writing for both first and inverse relationships.

- In mosts cases, [relationship examples](https://laravel.com/docs/6.x/eloquent-relationships) used in the official Laravel documentation will be used in this post. Your use-cases may be different, simply adapt the tests to your specific scenario.
- there are places where I included more than one method of testing, any of them is just fine.
- Watch out! Whenever a new model is introduced I add a schema test. Do not be surprised if some files have more test than others.

### 1. One To One Relationship

> A one-to-one relationship is used to define relationships where a single model has a unary relationship with another model. Both models can own only one instance of the each other.

#### Scenario:

We have **User** and **Phone** models. A user is allowed to register only one phone number on the app, this means one unique phone entry for a given user in the phone table. This relationship can pan out as **hasOne()** and **belongsTo()** relationships.

#### **hasOne()**

— A user **_has one_** phone registered (FIRST).

```
// App/User.php
...
public function phone() 
{ 
 return $this-\>hasOne(Phone::class); 
}
```

{% gist https://gist.github.com/tonyfrenzy/5b233ab172a460a237c55a64b4987f25 %}

**THE TEST** : A user owns a phone, a phone is a unique entity thus we test that the phone is an **_instanceOf_** a Phone class. Second method tests existence of a relationship by asserting with a successful count.

#### **belongsTo()**

- — Any registered phone **_belongs to_** a single user
- — INVERSE of **_hasOne_** relationship.

```
// App/Phone.php  
...
public function user() 
{ 
 return $this-\>belongsTo(User::class); 
}
```

{% gist https://gist.github.com/tonyfrenzy/94c21c7666b2403189bfbd3fac73254d %}

**THE TEST** : A phone belongs to a user, a user is a unique entity thus we tested that a user is an **_instanceOf_** a User class.

Files relevant to this test 👇

```
- **MODEL FILES** (App/)
 -- User.php 
 -- Phone.php

- **MIGRATION FILES** (database/migrations/)
 -- 2014\10\12\000000\create\users\table.php
 -- 2019\09\26\154439\create\phones\table.php

- **MODEL FACTORY FILES** (database/factories/)
 -- UserFactory.php 
 -- PhoneFactory.php

- **UNIT TEST FILES** (tests/Units/)
 -- UserTest.php 
 -- PhonesTest.php
```

Simply run the test suite and you should get a green.

`$ pu or putu (or any nice alias you gave to your unit test commands)`

or `$ vendor/bin/phpunit`

![](https://cdn-images-1.medium.com/max/720/1*z_G8V3yyXpVZMkYVcI4qKQ.png)<figcaption>All tests passed</figcaption>

**5 tests?** Yeah, schema test inclusive. See the list below:

```
1. User schema test 
2. Phone schema test
3. User-\>hasOne(phone) test (2 methods)
4. Phone-\>belongsTo(user) test
```

Tweak your code, goof around. Just have fun.

### 2. One To Many Relationship

> A one-to-many relationship is used to define relationships where a single model owns any amount of other models. — [Laravel.com](https://laravel.com/docs/6.x/eloquent-relationships#one-to-many)

#### Scenario:

A blog **_post_** with an infinite number of **_comments_** or an author (user) with many articles or comments. We are going to write a test for **Post** and **Comment** models relationship. Thus this relationship can pan out as **hasMany()** and **belongsTo()** relationships.

#### **hasMany()**

— A post **_has many_** comments on it (FIRST).

```
// App/Post.php
...
public function comments() 
{ 
 return $this-\>hasMany(Comment::class); 
}
```

{% gist https://gist.github.com/tonyfrenzy/09b215a61ae4ed00d95d5dabde5c721e %}

**THE TEST** : Since a post has many comments, the comments are returned as collections on the **_post instance_** that is why we are able to do iterations when rendering results in app views. It is correct that we test a post’s comments as an **_instanceOf_** a collection.

#### **belongsTo()**

- — A comment **_belongs to_** a post
- — INVERSE of **_hasMany_** relationship.

```
// App/Comment.php
...
public function user() 
{ 
 return $this-\>belongsTo(Post::class); 
}
```

{% gist https://gist.github.com/tonyfrenzy/c36236da38c751b42acfeacc123a1601 %}

**THE TEST** : A comment belongs to a post, a post is a unique entity thus we test that a post is an **_instanceOf_** a Post class. This is similar to $phone-\>user test we had earlier.

Files relevant to this test 👇

```
- **MODEL FILES** (App/)
 -- User.php 
 -- Post.php
 -- Comment.php

- **MIGRATION FILES** (database/migrations/)
 -- 2014\10\12\000000\create\users\table.php
 -- 2019\09\27\100604\create\posts\table.php
 -- 2019\09\27\100620\create\comments\table.php

- **MODEL FACTORY FILES** (database/factories/)
 -- UserFactory.php 
 -- PostFactory.php
 -- CommentFactory.php

- **UNIT TEST FILES** (tests/Units/)
 -- UserTest.php 
 -- PostsTest.php
 -- CommentsTest.php
```

If you run the test at this point it should all pass.

#### Heads up:

As depicted in the scenario section, this same **One To Many** tests can be replicated on **_User-Post_** , **_User-Comment_** and other model relationships that fit into the scenario _(I have all the tests in this_ **_Github repo_** )_. A user can create many posts and many comments as well, these are **hasMany()** relationships. Writing model relationship tests will become natural to you when you fully understand how the eloquent relationships work.

### 3. Many-to-Many Relationship

> A many-to-many relationship is used to define relationships where the two models involved can have many instances of each other.

#### Scenario:

A user can play many roles, and the roles are also shared by other users. Also for doctors and medical specialties, a specialty can be shared by many doctors while a doctor can be specialized in many specialties. Our test will be for **User** and **Role** models relationship.

**_belongsTo_** means a model that is already existing that another model fits into but have no privilege to create. This is different from _hasOne_ or _hasMany_ whose verb depict that a given model owns or creates another. On both models involved, the relationship is defined as **belongsToMany()**.

#### belongsToMany()

— A user **_belongs to many_** roles (FIRST).

```
// App/User.php
...
public function roles() 
{ 
 return $this-\>belongsToMany(Comment::class); 
}
```

{% gist https://gist.github.com/tonyfrenzy/92ff0f9ddea4c91d4027f3528fa8c646 %}

#### belongsToMany()

- — A role **_belongs to many_**  users
- — INVERSE of **_belongsToMany_** relationship. Kinda recursive.

```
// App/Role.php
...
public function users() 
{ 
 return $this-\>belongsToMany(User::class); 
}
```

{% gist https://gist.github.com/tonyfrenzy/5ddc9bdf96d2355c3e306a781228fb26 %}

**NB** : **_role\user_** table must be migrated for this tests to pass.

Files relevant to this test 👇

```
- **MODEL FILES** (App/)
 -- User.php 
 -- Role.php

- **MIGRATION FILES** (database/migrations/)
 -- 2014\10\12\000000\create\users\table.php
 -- 2019\09\27\155312\create\roles\table.php
 -- 2019\09\27\164342\create\role\user\table.php

- **MODEL FACTORY FILES** (database/factories/)
 -- UserFactory.php 
 -- RoleFactory.php

- **UNIT TEST FILES** (tests/Units/)
 -- UserTest.php 
 -- RolesTest.php
```

### 4. Has One Through Relationship

> The “[has-one-through](https://laravel.com/docs/6.x/eloquent-relationships#has-one-through)” relationship links models through a single intermediate relation.

#### Scenario:

Given a **Supplier** has one **User** , and each user is associated with a user **History** record, the supplier model may access the user’s history _through_ the intermediate user model.

#### hasOneThrough()

- — A supplier **_has one_** history **_through_** a user
- — has no official INVERSE relationship.

```
// App/Supplier.php
...
public function userHistory() 
{ 
 return $this-\>hasOneThrough(History::class, User::class); 
}
```

{% gist https://gist.github.com/tonyfrenzy/8d0c649467b4c2960c2addb0ab1e8002 %}

The “ **_hasThrough_** ” relationships do not have official reverse relationship since the concerned models are always indirectly related but a reverse can be hacked by linking through the intermediate model like below, however, beware of n+1 issues and eager load as appropriate.:

```
// App/History.php
...
/** 
 * With this attribute defined it can be used as **$this-\>supplier**
 */
public function getSupplierAttribute()
{
 return $this-\>user-\>supplier;
}
```

Files relevant to this test 👇

```
- **MODEL FILES** (App/)
 -- User.php 
 -- Supplier.php
 -- History.php

- **MIGRATION FILES** (database/migrations/)
 -- 2014\10\12\000000\create\users\table.php
 -- 2019\09\27\155312\create\roles\table.php
 -- 2019\09\27\164342\create\role\user\table.php

- **MODEL FACTORY FILES** (database/factories/)
 -- UserFactory.php 
 -- SuppliersFactory.php
 -- HistoryFactory.php

- **UNIT TEST FILES** (tests/Units/)
 -- UserTest.php 
 -- SuppliersTest.php
 -- HistoriesTest.php
```

### 5. Has Many Through Relationship

> Just like the “has-one-through” relationship links a distant related model through an intermediate relation, “ [has-many-through](https://laravel.com/docs/6.x/eloquent-relationships#has-many-through)” does same but it **can reference many instances** of the distant related model.

#### Scenario:

A Country model might have many Post models through an intermediate User model. We will write a test that ensures blog posts for a given country can be accessed on the country instance by a **_hasManyThrough_** relationship.

#### hasManyThrough()

- — A country can **_have many_** posts **_through_** a user model
- — has no official REVERSE relationship

```
// App/Country.php
...
public function posts() 
{ 
 return $this-\>hasManyThrough(Post::class, User::class); 
}
```

{% gist https://gist.github.com/tonyfrenzy/3651354341fafd9f47938093b425fbaf %}

**NB:** We now make use of a parent::setup() method to DRY the test codes.

The “ **_hasThrough_** ” relationships do not have official reverse relationship but a reverse can be hacked by linking through the intermediate model like below, (beware of n+1 issues and eager load as appropriate):

```
// App/Post.php
...
/** 
 * With this attribute defined it can be used as **$this-\>country**
 */
public function getCountryAttribute()
{
 return $this-\>user-\>country;
}
```

Files relevant to this test 👇

```
- **MODEL FILES** (App/)
 -- User.php 
 -- Country.php
 -- Post.php

- **MIGRATION FILES** (database/migrations/)
 -- 2014\10\12\000000\create\users\table.php
 -- 2019\09\27\100604\create\posts\table.php
 -- 2019\09\28\130218\create\countries\table.php
 -- 2019\09\28\131328\add\country\id\to\users\table.php

- **MODEL FACTORY FILES** (database/factories/)
 -- UserFactory.php 
 -- CountryFactory.php
 -- PostFactory.php

- **UNIT TEST FILES** (tests/Units/)
 -- UserTest.php 
 -- CountriesTest.php
 -- PostsTest.php
```

See the polymorphic relationship tests in the next post.

- 👉Part 1: [**Intro and Schema Tests**](https://dev.to/tonyfrenzy/part-1-testing-model-relationships-in-laravel-schema-tests-37nj)
- ⏯Part 2: **Testing Basic Model Relationships**
- 👉Part 3: [**Testing Polymorphic Relationships**](https://dev.to/tonyfrenzy/part-3-testing-model-relationships-in-laravel-polymorphic-m51)