diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/DeeplyNestedInclusionTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/DeeplyNestedInclusionTests.cs
index 7af5c3b594..5e4754c7c5 100644
--- a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/DeeplyNestedInclusionTests.cs
+++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/DeeplyNestedInclusionTests.cs
@@ -1,10 +1,13 @@
+using System.Collections.Generic;
 using System.Net;
 using System.Net.Http;
 using System.Threading.Tasks;
 using Bogus;
+using JsonApiDotNetCore.Models;
 using JsonApiDotNetCoreExample;
 using JsonApiDotNetCoreExample.Data;
 using JsonApiDotNetCoreExample.Models;
+using JsonApiDotNetCoreExampleTests.Helpers.Extensions;
 using Microsoft.AspNetCore.Hosting;
 using Newtonsoft.Json;
 using Xunit;
@@ -22,6 +25,14 @@ public DeeplyNestedInclusionTests(TestFixture<TestStartup> fixture)
             _fixture = fixture;
         }
 
+        private void ResetContext(AppDbContext context) 
+        {
+            context.TodoItems.RemoveRange(context.TodoItems);
+            context.TodoItemCollections.RemoveRange(context.TodoItemCollections);
+            context.People.RemoveRange(context.People);
+            context.PersonRoles.RemoveRange(context.PersonRoles);
+        }
+
         [Fact]
         public async Task Can_Include_Nested_Relationships()
         {
@@ -53,5 +64,253 @@ public async Task Can_Include_Nested_Relationships()
             Assert.NotNull(responseTodoItem.Collection);
             Assert.NotNull(responseTodoItem.Collection.Owner);
         }
+
+        [Fact]
+        public async Task Can_Include_Nested_HasMany_Relationships()
+        {
+            // arrange
+            const string route = "/api/v1/todo-items?include=collection.todo-items";
+
+            var todoItem = new TodoItem {
+                Collection = new TodoItemCollection {
+                    Owner = new Person(),
+                    TodoItems = new List<TodoItem> {
+                        new TodoItem(),
+                        new TodoItem()
+                    }
+                }
+            };
+        
+            
+            var context = _fixture.GetService<AppDbContext>();
+            ResetContext(context);
+
+            context.TodoItems.Add(todoItem);
+            await context.SaveChangesAsync();
+
+            // act
+            var response = await _fixture.Client.GetAsync(route);
+
+            // assert
+            Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+
+            var body = await response.Content.ReadAsStringAsync();
+            var documents = JsonConvert.DeserializeObject<Documents>(body);
+            var included = documents.Included;
+            
+            Assert.Equal(4, included.Count);
+
+            Assert.Equal(3, included.CountOfType("todo-items"));
+            Assert.Equal(1, included.CountOfType("todo-collections"));
+        }
+
+        [Fact]
+        public async Task Can_Include_Nested_HasMany_Relationships_BelongsTo()
+        {
+            // arrange
+            const string route = "/api/v1/todo-items?include=collection.todo-items.owner";
+
+            var todoItem = new TodoItem {
+                Collection = new TodoItemCollection {
+                    Owner = new Person(),
+                    TodoItems = new List<TodoItem> {
+                        new TodoItem {
+                            Owner = new Person()
+                        },
+                        new TodoItem()
+                    }
+                }
+            };
+        
+            var context = _fixture.GetService<AppDbContext>();
+            ResetContext(context);
+
+            context.TodoItems.Add(todoItem);
+            await context.SaveChangesAsync();
+
+            // act
+            var response = await _fixture.Client.GetAsync(route);
+
+            // assert
+            Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+
+            var body = await response.Content.ReadAsStringAsync();
+            var documents = JsonConvert.DeserializeObject<Documents>(body);
+            var included = documents.Included;
+            
+            Assert.Equal(5, included.Count);
+
+            Assert.Equal(3, included.CountOfType("todo-items"));
+            Assert.Equal(1, included.CountOfType("people"));
+            Assert.Equal(1, included.CountOfType("todo-collections"));
+        }
+
+        [Fact]
+        public async Task Can_Include_Nested_Relationships_With_Multiple_Paths()
+        {
+            // arrange
+            const string route = "/api/v1/todo-items?include=collection.owner.role,collection.todo-items.owner";
+
+            var todoItem = new TodoItem {
+                Collection = new TodoItemCollection {
+                    Owner = new Person {
+                        Role = new PersonRole()
+                    },
+                    TodoItems = new List<TodoItem> {
+                        new TodoItem {
+                            Owner = new Person()
+                        },
+                        new TodoItem()
+                    }
+                }
+            };
+        
+            var context = _fixture.GetService<AppDbContext>();
+            ResetContext(context);
+
+            context.TodoItems.Add(todoItem);
+            await context.SaveChangesAsync();
+
+            // act
+            var response = await _fixture.Client.GetAsync(route);
+
+            // assert
+            Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+
+            var body = await response.Content.ReadAsStringAsync();
+            var documents = JsonConvert.DeserializeObject<Documents>(body);
+            var included = documents.Included;
+            
+            Assert.Equal(7, included.Count);
+            
+            Assert.Equal(3, included.CountOfType("todo-items"));
+            Assert.Equal(2, included.CountOfType("people"));
+            Assert.Equal(1, included.CountOfType("person-roles"));
+            Assert.Equal(1, included.CountOfType("todo-collections"));
+        }
+
+        [Fact]
+        public async Task Included_Resources_Are_Correct()
+        {
+            // arrange
+            var role = new PersonRole();
+            var assignee = new Person { Role = role };
+            var collectionOwner = new Person();
+            var someOtherOwner = new Person();
+            var collection = new TodoItemCollection { Owner = collectionOwner };
+            var todoItem1 = new TodoItem { Collection = collection, Assignee = assignee };
+            var todoItem2 = new TodoItem { Collection = collection, Assignee = assignee };
+            var todoItem3 = new TodoItem { Collection = collection, Owner = someOtherOwner };
+            var todoItem4 = new TodoItem { Collection = collection, Owner = assignee };
+        
+            var context = _fixture.GetService<AppDbContext>();
+            ResetContext(context);
+
+            context.TodoItems.Add(todoItem1);
+            context.TodoItems.Add(todoItem2);
+            context.TodoItems.Add(todoItem3);
+            context.TodoItems.Add(todoItem4);
+            context.PersonRoles.Add(role);
+            context.People.Add(assignee);
+            context.People.Add(collectionOwner);
+            context.People.Add(someOtherOwner);
+            context.TodoItemCollections.Add(collection);
+
+
+            await context.SaveChangesAsync();
+
+            string route = 
+                "/api/v1/todo-items/" + todoItem1.Id + "?include=" +
+                    "collection.owner," + 
+                    "assignee.role," + 
+                    "assignee.assigned-todo-items";
+
+            // act
+            var response = await _fixture.Client.GetAsync(route);
+
+            // assert
+            Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+
+            var body = await response.Content.ReadAsStringAsync();
+            var documents = JsonConvert.DeserializeObject<Document>(body);
+            var included = documents.Included;
+            
+            // 1 collection, 1 owner, 
+            // 1 assignee, 1 assignee role,
+            // 2 assigned todo items (including the primary resource)
+            Assert.Equal(6, included.Count); 
+
+            var collectionDocument = included.FindResource("todo-collections", collection.Id);
+            var ownerDocument = included.FindResource("people", collectionOwner.Id);
+            var assigneeDocument = included.FindResource("people", assignee.Id);
+            var roleDocument = included.FindResource("person-roles", role.Id);
+            var assignedTodo1 = included.FindResource("todo-items", todoItem1.Id);
+            var assignedTodo2 = included.FindResource("todo-items", todoItem2.Id);
+
+            Assert.NotNull(assignedTodo1);
+            Assert.Equal(todoItem1.Id.ToString(), assignedTodo1.Id);
+
+            Assert.NotNull(assignedTodo2);
+            Assert.Equal(todoItem2.Id.ToString(), assignedTodo2.Id);
+
+            Assert.NotNull(collectionDocument);
+            Assert.Equal(collection.Id.ToString(), collectionDocument.Id);
+
+            Assert.NotNull(ownerDocument);
+            Assert.Equal(collectionOwner.Id.ToString(), ownerDocument.Id);
+
+            Assert.NotNull(assigneeDocument);
+            Assert.Equal(assignee.Id.ToString(), assigneeDocument.Id);
+
+            Assert.NotNull(roleDocument);
+            Assert.Equal(role.Id.ToString(), roleDocument.Id);
+        }        
+
+        [Fact]
+        public async Task Can_Include_Doubly_HasMany_Relationships()
+        {
+            // arrange
+            var person = new Person {
+                TodoItemCollections = new List<TodoItemCollection> {
+                    new TodoItemCollection {
+                        TodoItems = new List<TodoItem> {
+                            new TodoItem(),
+                            new TodoItem()
+                        }
+                    },
+                    new TodoItemCollection {
+                        TodoItems = new List<TodoItem> {
+                            new TodoItem(),
+                            new TodoItem(),
+                            new TodoItem()
+                        }
+                    }
+                }
+            };
+        
+            var context = _fixture.GetService<AppDbContext>();
+            ResetContext(context);
+
+            context.People.Add(person);
+
+            await context.SaveChangesAsync();
+
+            string route = "/api/v1/people/" + person.Id + "?include=todo-collections.todo-items";
+
+            // act
+            var response = await _fixture.Client.GetAsync(route);
+
+            // assert
+            Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+
+            var body = await response.Content.ReadAsStringAsync();
+            var documents = JsonConvert.DeserializeObject<Document>(body);
+            var included = documents.Included;
+            
+            Assert.Equal(7, included.Count); 
+
+            Assert.Equal(5, included.CountOfType("todo-items"));
+            Assert.Equal(2, included.CountOfType("todo-collections"));
+        }        
     }
 }
\ No newline at end of file
diff --git a/test/JsonApiDotNetCoreExampleTests/Helpers/Extensions/DocumentExtensions.cs b/test/JsonApiDotNetCoreExampleTests/Helpers/Extensions/DocumentExtensions.cs
new file mode 100644
index 0000000000..f467e17f5b
--- /dev/null
+++ b/test/JsonApiDotNetCoreExampleTests/Helpers/Extensions/DocumentExtensions.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using JsonApiDotNetCore.Models;
+using Microsoft.EntityFrameworkCore.Internal;
+using Microsoft.EntityFrameworkCore.Query;
+using Microsoft.EntityFrameworkCore.Query.Internal;
+using Microsoft.EntityFrameworkCore.Storage;
+using Database = Microsoft.EntityFrameworkCore.Storage.Database;
+
+namespace JsonApiDotNetCoreExampleTests.Helpers.Extensions
+{
+    public static class DocumentExtensions
+    {
+        public static DocumentData FindResource<TId>(this List<DocumentData> included, string type, TId id)
+        {
+            var document = included.Where(documentData => (
+                documentData.Type == type 
+                && documentData.Id == id.ToString()
+            )).FirstOrDefault();
+
+            return document;
+        }
+
+        public static int CountOfType(this List<DocumentData> included, string type) {
+            return included.Where(documentData => documentData.Type == type).Count();
+        }
+    }
+}
\ No newline at end of file
diff --git a/test/UnitTests/Serialization/JsonApiSerializerTests.cs b/test/UnitTests/Serialization/JsonApiSerializerTests.cs
index 8a67b80434..c6b4a09128 100644
--- a/test/UnitTests/Serialization/JsonApiSerializerTests.cs
+++ b/test/UnitTests/Serialization/JsonApiSerializerTests.cs
@@ -1,9 +1,15 @@
-using JsonApiDotNetCore.Builders;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text.RegularExpressions;
+using JsonApiDotNetCore.Builders;
 using JsonApiDotNetCore.Configuration;
+using JsonApiDotNetCore.Extensions;
 using JsonApiDotNetCore.Internal;
 using JsonApiDotNetCore.Models;
+using JsonApiDotNetCore.Request;
 using JsonApiDotNetCore.Serialization;
 using JsonApiDotNetCore.Services;
+using Microsoft.Extensions.DependencyInjection;
 using Moq;
 using Xunit;
 
@@ -17,19 +23,9 @@ public void Can_Serialize_Complex_Types()
             // arrange
             var contextGraphBuilder = new ContextGraphBuilder();
             contextGraphBuilder.AddResource<TestResource>("test-resource");
-            var contextGraph = contextGraphBuilder.Build();
-
-            var jsonApiContextMock = new Mock<IJsonApiContext>();
-            jsonApiContextMock.SetupAllProperties();
-            jsonApiContextMock.Setup(m => m.ContextGraph).Returns(contextGraph);
-            jsonApiContextMock.Setup(m => m.Options).Returns(new JsonApiOptions());
-            jsonApiContextMock.Setup(m => m.RequestEntity)
-                .Returns(contextGraph.GetContextEntity("test-resource"));
-            jsonApiContextMock.Setup(m => m.MetaBuilder).Returns(new MetaBuilder());
-            jsonApiContextMock.Setup(m => m.PageManager).Returns(new PageManager());
+          
+            var serializer = GetSerializer(contextGraphBuilder);
 
-            var documentBuilder = new DocumentBuilder(jsonApiContextMock.Object);
-            var serializer = new JsonApiSerializer(jsonApiContextMock.Object, documentBuilder);
             var resource = new TestResource
             {
                 ComplexMember = new ComplexType
@@ -43,18 +39,235 @@ public void Can_Serialize_Complex_Types()
 
             // assert
             Assert.NotNull(result);
-            Assert.Equal("{\"data\":{\"attributes\":{\"complex-member\":{\"compound-name\":\"testname\"}},\"type\":\"test-resource\",\"id\":\"\"}}", result);
+
+            var expectedFormatted = 
+            @"{
+                ""data"": {
+                    ""attributes"": {
+                        ""complex-member"": {
+                            ""compound-name"": ""testname""
+                        }
+                    },
+                    ""relationships"": {
+                        ""children"": {
+                            ""links"": {
+                                ""self"": ""/test-resource//relationships/children"",
+                                ""related"": ""/test-resource//children""
+                            }
+                        }
+                    },
+                    ""type"": ""test-resource"",
+                    ""id"": """"
+                }
+            }";
+            var expected = Regex.Replace(expectedFormatted, @"\s+", "");
+            
+            Assert.Equal(expected, result);
+        }
+
+        [Fact]
+        public void Can_Serialize_Deeply_Nested_Relationships()
+        {
+            // arrange
+            var contextGraphBuilder = new ContextGraphBuilder();
+            contextGraphBuilder.AddResource<TestResource>("test-resource");
+            contextGraphBuilder.AddResource<ChildResource>("children");
+            contextGraphBuilder.AddResource<InfectionResource>("infections");
+          
+            var serializer = GetSerializer(
+                contextGraphBuilder,
+                included: new List<string> { "children.infections" }
+            );
+
+            var resource = new TestResource
+            {
+                Id = 1,
+                Children = new List<ChildResource> {
+                    new ChildResource {
+                        Id = 2,
+                        Infections = new List<InfectionResource> {
+                            new InfectionResource { Id = 4 },
+                            new InfectionResource { Id = 5 },
+                        }
+                    },
+                    new ChildResource {
+                        Id = 3
+                    }
+                }
+            };
+
+            // act
+            var result = serializer.Serialize(resource);
+
+            // assert
+            Assert.NotNull(result);
+            
+            var expectedFormatted = 
+            @"{
+                ""data"": {
+                    ""attributes"": {
+                        ""complex-member"": null
+                    },
+                    ""relationships"": {
+                        ""children"": {
+                            ""links"": {
+                                ""self"": ""/test-resource/1/relationships/children"",
+                                ""related"": ""/test-resource/1/children""
+                            },
+                            ""data"": [{
+                                ""type"": ""children"",
+                                ""id"": ""2""
+                            }, {
+                                ""type"": ""children"",
+                                ""id"": ""3""
+                            }]
+                        }
+                    },
+                    ""type"": ""test-resource"",
+                    ""id"": ""1""
+                },
+                ""included"": [
+                    {
+                        ""attributes"": {},
+                        ""relationships"": {
+                            ""infections"": {
+                                ""links"": {
+                                    ""self"": ""/children/2/relationships/infections"",
+                                    ""related"": ""/children/2/infections""
+                                },
+                                ""data"": [{
+                                    ""type"": ""infections"",
+                                    ""id"": ""4""
+                                }, {
+                                    ""type"": ""infections"",
+                                    ""id"": ""5""
+                                }]
+                            },
+                            ""parent"": {
+                                ""links"": {
+                                    ""self"": ""/children/2/relationships/parent"",
+                                    ""related"": ""/children/2/parent""
+                                }
+                            }
+                        },
+                        ""type"": ""children"",
+                        ""id"": ""2""
+                    },
+                    {
+                        ""attributes"": {},
+                        ""relationships"": {
+                            ""infected"": {
+                                ""links"": {
+                                    ""self"": ""/infections/4/relationships/infected"",
+                                    ""related"": ""/infections/4/infected""
+                                }
+                            }
+                        },
+                        ""type"": ""infections"",
+                        ""id"": ""4""
+                    },
+                    {
+                        ""attributes"": {},
+                        ""relationships"": {
+                            ""infected"": {
+                                ""links"": {
+                                    ""self"": ""/infections/5/relationships/infected"",
+                                    ""related"": ""/infections/5/infected""
+                                }
+                            }
+                        },
+                        ""type"": ""infections"",
+                        ""id"": ""5""
+                    },
+                    {
+                        ""attributes"": {},
+                        ""relationships"": {
+                            ""infections"": {
+                                ""links"": {
+                                    ""self"": ""/children/3/relationships/infections"",
+                                    ""related"": ""/children/3/infections""
+                                }
+                            },
+                            ""parent"": {
+                                ""links"": {
+                                    ""self"": ""/children/3/relationships/parent"",
+                                    ""related"": ""/children/3/parent""
+                                }
+                            }
+                        },
+                        ""type"": ""children"",
+                        ""id"": ""3""
+                    }
+                ]
+            }";
+            var expected = Regex.Replace(expectedFormatted, @"\s+", "");
+
+            Assert.Equal(expected, result);
+        }
+
+        private JsonApiSerializer GetSerializer(
+            ContextGraphBuilder contextGraphBuilder, 
+            List<string> included = null)
+        {
+            var contextGraph = contextGraphBuilder.Build();
+
+            var jsonApiContextMock = new Mock<IJsonApiContext>();
+            jsonApiContextMock.SetupAllProperties();
+            jsonApiContextMock.Setup(m => m.ContextGraph).Returns(contextGraph);
+            jsonApiContextMock.Setup(m => m.Options).Returns(new JsonApiOptions());
+            jsonApiContextMock.Setup(m => m.RequestEntity).Returns(contextGraph.GetContextEntity("test-resource"));
+            // jsonApiContextMock.Setup(m => m.AttributesToUpdate).Returns(new Dictionary<AttrAttribute, object>());
+            // jsonApiContextMock.Setup(m => m.RelationshipsToUpdate).Returns(new Dictionary<RelationshipAttribute, object>());
+            // jsonApiContextMock.Setup(m => m.HasManyRelationshipPointers).Returns(new HasManyRelationshipPointers());
+            // jsonApiContextMock.Setup(m => m.HasOneRelationshipPointers).Returns(new HasOneRelationshipPointers());
+            jsonApiContextMock.Setup(m => m.MetaBuilder).Returns(new MetaBuilder());
+            jsonApiContextMock.Setup(m => m.PageManager).Returns(new PageManager());
+
+            if (included != null)
+                jsonApiContextMock.Setup(m => m.IncludedRelationships).Returns(included);
+
+            var jsonApiOptions = new JsonApiOptions();
+            jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions);
+
+            var services = new ServiceCollection();
+
+            var mvcBuilder = services.AddMvcCore();
+
+            services
+                .AddJsonApiInternals(jsonApiOptions);
+
+            var provider = services.BuildServiceProvider();
+            var scoped = new TestScopedServiceProvider(provider);
+
+            var documentBuilder = new DocumentBuilder(jsonApiContextMock.Object, scopedServiceProvider: scoped);
+            var serializer = new JsonApiSerializer(jsonApiContextMock.Object, documentBuilder);
+
+            return serializer;
         }
 
         private class TestResource : Identifiable
         {
             [Attr("complex-member")]
             public ComplexType ComplexMember { get; set; }
+
+            [HasMany("children")] public List<ChildResource> Children { get; set; }
         }
 
         private class ComplexType
         {
             public string CompoundName { get; set; }
         }
+
+        private class ChildResource : Identifiable
+        {
+            [HasMany("infections")] public List<InfectionResource> Infections { get; set;}
+
+            [HasOne("parent")] public TestResource Parent { get; set; }
+        }
+
+        private class InfectionResource : Identifiable
+        {
+            [HasOne("infected")] public ChildResource Infected { get; set; }
+        }
     }
 }