Skip to content

Commit 93d8ab9

Browse files
authored
Work/v1 (#4)
* Adding unit tests * Added unit tests and fixed issue with TypeExtensions
1 parent 90d0980 commit 93d8ab9

11 files changed

Lines changed: 362 additions & 14 deletions

File tree

QueryPattern.sln

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QuerySample", "sample\Query
1515
EndProject
1616
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "myNOC.Tests.EntityFramework.Query", "tests\myNOC.Tests.EntityFramework.Query\myNOC.Tests.EntityFramework.Query.csproj", "{71B8019E-AF04-49FF-965C-DC106A3FC7C9}"
1717
EndProject
18+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{DFFF835B-547A-4D6F-B739-7CBAE2142AD2}"
19+
EndProject
20+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{1ABF61F2-FD86-4BFF-A509-64D673DF9913}"
21+
ProjectSection(SolutionItems) = preProject
22+
.github\workflows\build-tests.yml = .github\workflows\build-tests.yml
23+
.github\workflows\release.yml = .github\workflows\release.yml
24+
EndProjectSection
25+
EndProject
1826
Global
1927
GlobalSection(SolutionConfigurationPlatforms) = preSolution
2028
Debug|Any CPU = Debug|Any CPU
@@ -41,6 +49,7 @@ Global
4149
{BD5A2D1A-6761-4F89-9573-406079505724} = {A506A935-71CF-4D25-A7C4-FBE112BCFFDD}
4250
{06AA0485-2E8B-4592-970A-7EF12C6149C2} = {2F1180C1-B4C8-4A4F-A348-68D90ABD7787}
4351
{71B8019E-AF04-49FF-965C-DC106A3FC7C9} = {CFA418EB-F1B2-4BAF-A725-31EBAD0DC1D7}
52+
{1ABF61F2-FD86-4BFF-A509-64D673DF9913} = {DFFF835B-547A-4D6F-B739-7CBAE2142AD2}
4453
EndGlobalSection
4554
GlobalSection(ExtensibilityGlobals) = postSolution
4655
SolutionGuid = {43AABAA3-16E5-4467-9DAE-6388878E4C1C}

QueryPattern.v3.ncrunchsolution

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<SolutionConfiguration>
2+
<Settings>
3+
<AllowParallelTestExecution>True</AllowParallelTestExecution>
4+
<SolutionConfigured>True</SolutionConfigured>
5+
</Settings>
6+
</SolutionConfiguration>

src/myNOC.EntityFramework.Query/Extensions/TypeExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ private static void GetServiceInterfaces(TypeInterfaces typeInterfaces, Type int
1919
{
2020
foreach(var it in GetServiceInterfaces(typeInterfaces.Type))
2121
{
22-
if (it.IsAssignableTo(interfaceType) && it != interfaceType)
22+
if (it.IsAssignableTo(interfaceType))
2323
typeInterfaces.Interfaces.Add(it);
2424
}
2525
}

src/myNOC.EntityFramework.Query/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ Once you have your `queryRepo` you can execute the query.
174174
var result = await queryRepo.Query(new ContactNameContains("a"));
175175
```
176176

177-
`ContactNameContains` inherits from `IQueryList<ContactModal>` so the `Query` method will return `IEnumerable<ContactModel>` where the name contains an `a`.
177+
`ContactNameContains` inherits from `IQueryList<ContactModel>` so the `Query` method will return `IEnumerable<ContactModel>` where the name contains an `a`.
178178

179179
You run a scalar query the same way.
180180

@@ -218,4 +218,4 @@ static async Task SeedSampleData()
218218
addressBook.Add(new ContactEntity { Id = 4, Name = "David" });
219219
await addressBook.SaveChangesAsync();
220220
}
221-
```
221+
```
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using myNOC.EntityFramework.Query.Extensions;
2+
3+
namespace myNOC.Tests.EntityFramework.Query.Extensions
4+
{
5+
[TestClass]
6+
public class IEnumerableExtensionsTests
7+
{
8+
[TestMethod]
9+
public void Apply_ExecutesAction_ReturnsIEnumerable()
10+
{
11+
// Assemble
12+
var numbers = new List<int> { 1, 2, 3, 4, 5, 6 };
13+
var total = numbers.Sum();
14+
var calculated = 0;
15+
16+
// Act
17+
var results = numbers.AsEnumerable().Apply(x => calculated += x);
18+
19+
// Assert
20+
Assert.AreEqual(6, results.Count());
21+
Assert.AreEqual(total, calculated);
22+
}
23+
}
24+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
using Microsoft.Extensions.DependencyInjection;
2+
using myNOC.EntityFramework.Query;
3+
using myNOC.EntityFramework.Query.Extensions;
4+
5+
namespace myNOC.Tests.EntityFramework.Query.Extensions
6+
{
7+
[TestClass]
8+
public class IServiceCollectionExtensionsTests
9+
{
10+
[TestMethod]
11+
public void AddQueryPattern_ScanAppDomainForClassesThatInheritFromIQueryContextOrIQueryRepository_ScopedAndRegister()
12+
{
13+
// Assemble
14+
var services = new ServiceCollection();
15+
16+
// Act
17+
var results = services.AddQueryPattern();
18+
19+
// Assert
20+
Assert.IsNotNull(results);
21+
Assert.IsInstanceOfType(results, typeof(IServiceCollection));
22+
23+
Assert.IsNotNull(results.FirstOrDefault(x => x.ImplementationType == typeof(TestQueryContext)
24+
&& x.ServiceType == typeof(IQueryContext)
25+
&& x.Lifetime == ServiceLifetime.Scoped));
26+
27+
Assert.IsNotNull(results.FirstOrDefault(x => x.ImplementationType == typeof(TestQueryRepository)
28+
&& x.ServiceType == typeof(IQueryRepository)
29+
&& x.Lifetime == ServiceLifetime.Scoped));
30+
}
31+
32+
internal class TestQueryContext : IQueryContext
33+
{
34+
public IQueryable<TEntity> Set<TEntity>() where TEntity : class
35+
{
36+
throw new NotImplementedException();
37+
}
38+
}
39+
40+
internal class TestQueryRepository : IQueryRepository
41+
{
42+
public Task<IEnumerable<TModel>> Query<TModel>(IQueryList<TModel> query) where TModel : class
43+
{
44+
throw new NotImplementedException();
45+
}
46+
47+
public Task<TReturn?> Query<TReturn>(IQueryScalar<TReturn> query)
48+
{
49+
throw new NotImplementedException();
50+
}
51+
}
52+
}
53+
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
using myNOC.EntityFramework.Query.Extensions;
2+
using System.Reflection;
3+
4+
namespace myNOC.Tests.EntityFramework.Query.Extensions
5+
{
6+
[TestClass]
7+
public class TypeExtensionTests
8+
{
9+
private IEnumerable<Type> _types = default!;
10+
11+
[TestInitialize]
12+
public void Initialize()
13+
{
14+
_types = Assembly.GetExecutingAssembly().GetTypes();
15+
}
16+
17+
[TestMethod]
18+
public void CanImplement_Types_ImplementsInterface_DirectInterface_ReturnsTypesThatInheritFromITestInterface2()
19+
{
20+
// Arrange
21+
var implementedInterface = typeof(ITestInterface2);
22+
23+
// Act
24+
var results = _types.CanImplement(implementedInterface);
25+
26+
// Assert
27+
Assert.IsNotNull(results);
28+
Assert.AreEqual(1, results.Count());
29+
Assert.AreEqual(1, results[0].Interfaces.Count());
30+
Assert.AreEqual(implementedInterface, results[0].Interfaces[0]);
31+
Assert.AreEqual(typeof(TestClass3), results[0].Type);
32+
Assert.IsNull(results.FirstOrDefault(x => x.Type == typeof(TestClass4)));
33+
}
34+
35+
[TestMethod]
36+
public void CanImplement_Types_ImplementsInterface_SupportsNestedInterfaces_ReturnsTypesThatInheritFromITestInterface()
37+
{
38+
// Arrange
39+
var implementedInterface = typeof(ITestInterface);
40+
41+
// Act
42+
var results = _types.CanImplement(implementedInterface);
43+
44+
// Assert
45+
Assert.IsNotNull(results);
46+
Assert.AreEqual(3, results.Count());
47+
Assert.IsNotNull(results.FirstOrDefault(x => x.Type == typeof(TestClass1)));
48+
Assert.IsNotNull(results.FirstOrDefault(x => x.Type == typeof(TestClass2)));
49+
Assert.IsNotNull(results.FirstOrDefault(x => x.Type == typeof(TestClass5)));
50+
Assert.IsNull(results.FirstOrDefault(x => x.Type == typeof(TestClass4)));
51+
}
52+
53+
[TestMethod]
54+
public void CanImplement_Types_ImplementsInterface_NestedClassesRecursion()
55+
{
56+
// Arrange
57+
var implementedInterface = typeof(ITestInterface3);
58+
59+
// Act
60+
var results = _types.CanImplement(implementedInterface);
61+
62+
// Assert
63+
Assert.IsNotNull(results);
64+
Assert.AreEqual(2, results.Count());
65+
Assert.IsNotNull(results.FirstOrDefault(x => x.Type == typeof(TestClass6)));
66+
Assert.IsNotNull(results.FirstOrDefault(x => x.Type == typeof(TestClass8)));
67+
Assert.IsNull(results.FirstOrDefault(x => x.Type == typeof(TestClass4)));
68+
}
69+
70+
[TestMethod]
71+
public void CanImplement_Types_ImplementsInterface_SkipIDisposableInterfaceInGetInterfaces()
72+
{
73+
// Arrange
74+
var implementedInterface = typeof(ITestInterface5);
75+
76+
// Act
77+
var results = _types.CanImplement(implementedInterface);
78+
79+
// Assert
80+
Assert.IsNotNull(results);
81+
Assert.AreEqual(1, results.Count());
82+
Assert.IsNotNull(results.FirstOrDefault(x => x.Type.Equals(typeof(TestClass7))));
83+
Assert.IsNull(results.FirstOrDefault(x => x.Type == typeof(TestClass4)));
84+
}
85+
86+
internal interface ITestInterface { }
87+
internal interface ITestInterface2 { }
88+
internal interface ITestInterface3 { }
89+
internal interface ITestInterface4 : ITestInterface { }
90+
internal interface ITestInterface5 : IDisposable { }
91+
internal interface ITestInterface7 : ITestInterface3 { }
92+
93+
internal class TestClass1 : ITestInterface { }
94+
internal class TestClass2 : ITestInterface { }
95+
internal class TestClass3 : ITestInterface2 { }
96+
internal class TestClass4 { }
97+
internal class TestClass5 : ITestInterface4 { }
98+
internal class TestClass6 : TestClass4, ITestInterface7 { }
99+
internal class TestClass7 : ITestInterface5
100+
{
101+
public void Dispose()
102+
{
103+
throw new NotImplementedException();
104+
}
105+
}
106+
107+
internal class TestClass8 : TestClass6 { }
108+
}
109+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
using Microsoft.EntityFrameworkCore;
2+
using myNOC.EntityFramework.Query;
3+
using NSubstitute;
4+
using System.ComponentModel.DataAnnotations;
5+
6+
namespace myNOC.Tests.EntityFramework.Query
7+
{
8+
[TestClass]
9+
public class QueryContextTests
10+
{
11+
private TestContext _testDbContext = default!;
12+
private QueryContext _queryContext = Substitute.ForPartsOf<QueryContext>();
13+
14+
[TestInitialize]
15+
public void Initialize()
16+
{
17+
var options = new DbContextOptionsBuilder<TestContext>().UseInMemoryDatabase("testContext").Options;
18+
_testDbContext = new TestContext(options);
19+
}
20+
21+
22+
[TestCleanup]
23+
public void Cleanup()
24+
{
25+
_testDbContext?.Database?.EnsureDeleted();
26+
_testDbContext?.Dispose();
27+
}
28+
29+
[TestMethod]
30+
public void Set_Returns_Entity()
31+
{
32+
// Assemble
33+
_queryContext.GetContext().Returns(_testDbContext);
34+
35+
// Act
36+
var result = _queryContext.Set<TestEntity>();
37+
38+
// Assert
39+
Assert.IsNotNull(result);
40+
Assert.IsInstanceOfType<IQueryable<TestEntity>>(result);
41+
}
42+
43+
public class TestEntity
44+
{
45+
[Key]
46+
public int Id { get; set; }
47+
}
48+
49+
public class TestContext : DbContext
50+
{
51+
public DbSet<TestEntity> TestEntities { get; set; } = default!;
52+
53+
public TestContext(DbContextOptions options) : base(options) { }
54+
}
55+
}
56+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
using Microsoft.EntityFrameworkCore;
2+
using myNOC.EntityFramework.Query;
3+
using NSubstitute;
4+
using System.ComponentModel.DataAnnotations;
5+
6+
namespace myNOC.Tests.EntityFramework.Query
7+
{
8+
[TestClass]
9+
public class QueryRepositoryTests
10+
{
11+
private QueryContext _queryContext = default!;
12+
private TestContext _testDbContext = default!;
13+
private QueryRepository _queryRepository = default!;
14+
15+
[TestInitialize]
16+
public void Initialize()
17+
{
18+
var options = new DbContextOptionsBuilder<TestContext>().UseInMemoryDatabase("testRepository").Options;
19+
_testDbContext = new TestContext(options);
20+
_testDbContext.TestEntities.Add(new TestEntity());
21+
_testDbContext.TestEntities.Add(new TestEntity());
22+
_testDbContext.TestEntities.Add(new TestEntity());
23+
_testDbContext.SaveChanges();
24+
25+
_queryContext = Substitute.ForPartsOf<QueryContext>();
26+
_queryContext.GetContext().Returns(_testDbContext);
27+
28+
_queryRepository = Substitute.ForPartsOf<QueryRepository>(_queryContext);
29+
}
30+
31+
[TestCleanup]
32+
public void Cleanup()
33+
{
34+
_testDbContext?.Database?.EnsureDeleted();
35+
_testDbContext?.Dispose();
36+
}
37+
38+
[TestMethod]
39+
public async Task Query_RunAIQueryList_ReturnsIEnumerable()
40+
{
41+
// Assemble
42+
var query = new TestEntitiesGetAll();
43+
44+
// Act
45+
var result = await _queryRepository.Query(query);
46+
47+
// Assert
48+
Assert.AreEqual(3, result.Count());
49+
}
50+
51+
[TestMethod]
52+
public async Task Query_RunAIQueryScalar_ReturnsInt()
53+
{
54+
// Assemble
55+
var query = new TestEntitiesCount();
56+
57+
// Act
58+
var result = await _queryRepository.Query(query);
59+
60+
// Assert
61+
Assert.AreEqual(3, result);
62+
}
63+
64+
public class TestModel { }
65+
66+
public class TestEntity
67+
{
68+
[Key]
69+
public int Id { get; set; }
70+
}
71+
72+
public class TestContext : DbContext
73+
{
74+
public DbSet<TestEntity> TestEntities { get; set; } = default!;
75+
76+
public TestContext(DbContextOptions options) : base(options) { }
77+
}
78+
79+
internal class TestEntitiesGetAll : IQueryList<TestModel>
80+
{
81+
public IQueryable<TestModel> Query(IQueryContext context)
82+
{
83+
return from te in context.Set<TestEntity>()
84+
select new TestModel();
85+
}
86+
}
87+
88+
internal class TestEntitiesCount : IQueryScalar<int>
89+
{
90+
public async Task<int> GetScalar(IQueryContext context)
91+
{
92+
return await context.Set<TestEntity>().CountAsync();
93+
}
94+
}
95+
}
96+
}

0 commit comments

Comments
 (0)