├── .gitignore ├── README.md ├── Test ├── CoreAbstractions.Tests │ ├── CoreAbstractions.Tests.xproj │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Utilities │ │ └── UserUtilTests.cs │ └── project.json ├── ServerComponents.Tests │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── ServerComponents.Tests.xproj │ ├── Services │ │ ├── ReportServiceTests.cs │ │ └── UserServiceTests.cs │ └── project.json └── TestsCore │ ├── Properties │ └── AssemblyInfo.cs │ ├── TestingObject.cs │ ├── TestsCore.xproj │ └── project.json ├── UnitTestingExample.sln ├── global.json └── src ├── CoreAbstractions ├── CoreAbstractions.xproj ├── Entities │ ├── ReportMetadata.cs │ └── User.cs ├── Properties │ └── AssemblyInfo.cs ├── Utilities │ └── UserUtil.cs └── project.json ├── ServerAbstractions ├── IDatabaseContext.cs ├── Properties │ └── AssemblyInfo.cs ├── ServerAbstractions.xproj ├── Services │ ├── IReportService.cs │ └── IUserService.cs ├── SqlEntities │ ├── ReportMetadataSql.cs │ └── UserSql.cs └── project.json └── ServerComponents ├── Properties └── AssemblyInfo.cs ├── ServerComponents.xproj ├── Services ├── ReportService.cs └── UserService.cs └── project.json /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # MSTest test Results 33 | [Tt]est[Rr]esult*/ 34 | [Bb]uild[Ll]og.* 35 | 36 | # NUNIT 37 | *.VisualState.xml 38 | TestResult.xml 39 | 40 | # Build Results of an ATL Project 41 | [Dd]ebugPS/ 42 | [Rr]eleasePS/ 43 | dlldata.c 44 | 45 | # .NET Core 46 | project.lock.json 47 | project.fragment.lock.json 48 | artifacts/ 49 | **/Properties/launchSettings.json 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # Visual Studio code coverage results 117 | *.coverage 118 | *.coveragexml 119 | 120 | # NCrunch 121 | _NCrunch_* 122 | .*crunch*.local.xml 123 | nCrunchTemp_* 124 | 125 | # MightyMoose 126 | *.mm.* 127 | AutoTest.Net/ 128 | 129 | # Web workbench (sass) 130 | .sass-cache/ 131 | 132 | # Installshield output folder 133 | [Ee]xpress/ 134 | 135 | # DocProject is a documentation generator add-in 136 | DocProject/buildhelp/ 137 | DocProject/Help/*.HxT 138 | DocProject/Help/*.HxC 139 | DocProject/Help/*.hhc 140 | DocProject/Help/*.hhk 141 | DocProject/Help/*.hhp 142 | DocProject/Help/Html2 143 | DocProject/Help/html 144 | 145 | # Click-Once directory 146 | publish/ 147 | 148 | # Publish Web Output 149 | *.[Pp]ublish.xml 150 | *.azurePubxml 151 | # TODO: Comment the next line if you want to checkin your web deploy settings 152 | # but database connection strings (with potential passwords) will be unencrypted 153 | *.pubxml 154 | *.publishproj 155 | 156 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 157 | # checkin your Azure Web App publish settings, but sensitive information contained 158 | # in these scripts will be unencrypted 159 | PublishScripts/ 160 | 161 | # NuGet Packages 162 | *.nupkg 163 | # The packages folder can be ignored because of Package Restore 164 | **/packages/* 165 | # except build/, which is used as an MSBuild target. 166 | !**/packages/build/ 167 | # Uncomment if necessary however generally it will be regenerated when needed 168 | #!**/packages/repositories.config 169 | # NuGet v3's project.json files produces more ignoreable files 170 | *.nuget.props 171 | *.nuget.targets 172 | 173 | # Microsoft Azure Build Output 174 | csx/ 175 | *.build.csdef 176 | 177 | # Microsoft Azure Emulator 178 | ecf/ 179 | rcf/ 180 | 181 | # Windows Store app package directories and files 182 | AppPackages/ 183 | BundleArtifacts/ 184 | Package.StoreAssociation.xml 185 | _pkginfo.txt 186 | 187 | # Visual Studio cache files 188 | # files ending in .cache can be ignored 189 | *.[Cc]ache 190 | # but keep track of directories ending in .cache 191 | !*.[Cc]ache/ 192 | 193 | # Others 194 | ClientBin/ 195 | ~$* 196 | *~ 197 | *.dbmdl 198 | *.dbproj.schemaview 199 | *.jfm 200 | *.pfx 201 | *.publishsettings 202 | node_modules/ 203 | orleans.codegen.cs 204 | 205 | # Since there are multiple workflows, uncomment next line to ignore bower_components 206 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 207 | #bower_components/ 208 | 209 | # RIA/Silverlight projects 210 | Generated_Code/ 211 | 212 | # Backup & report files from converting an old project file 213 | # to a newer Visual Studio version. Backup files are not needed, 214 | # because we have git ;-) 215 | _UpgradeReport_Files/ 216 | Backup*/ 217 | UpgradeLog*.XML 218 | UpgradeLog*.htm 219 | 220 | # SQL Server files 221 | *.mdf 222 | *.ldf 223 | 224 | # Business Intelligence projects 225 | *.rdl.data 226 | *.bim.layout 227 | *.bim_*.settings 228 | 229 | # Microsoft Fakes 230 | FakesAssemblies/ 231 | 232 | # GhostDoc plugin setting file 233 | *.GhostDoc.xml 234 | 235 | # Node.js Tools for Visual Studio 236 | .ntvs_analysis.dat 237 | 238 | # Visual Studio 6 build log 239 | *.plg 240 | 241 | # Visual Studio 6 workspace options file 242 | *.opt 243 | 244 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 245 | *.vbw 246 | 247 | # Visual Studio LightSwitch build output 248 | **/*.HTMLClient/GeneratedArtifacts 249 | **/*.DesktopClient/GeneratedArtifacts 250 | **/*.DesktopClient/ModelManifest.xml 251 | **/*.Server/GeneratedArtifacts 252 | **/*.Server/ModelManifest.xml 253 | _Pvt_Extensions 254 | 255 | # Paket dependency manager 256 | .paket/paket.exe 257 | paket-files/ 258 | 259 | # FAKE - F# Make 260 | .fake/ 261 | 262 | # JetBrains Rider 263 | .idea/ 264 | *.sln.iml 265 | 266 | # CodeRush 267 | .cr/ 268 | 269 | # Python Tools for Visual Studio (PTVS) 270 | __pycache__/ 271 | *.pyc 272 | 273 | # Cake - Uncomment if you are using it 274 | # tools/** 275 | # !tools/packages.config -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UnitTestingExample 2 | Small example project that showcases some basic unit testing examples and best practices 3 | 4 | I've written a complete and thorough guide to unit testing using .NET Core, xUnit, and Moq that accompanies this sample project: 5 | [https://rushfive.github.io/Start-Unit-Testing-with-xUnit-Moq/](https://rushfive.github.io/Start-Unit-Testing-with-xUnit-Moq/) 6 | -------------------------------------------------------------------------------- /Test/CoreAbstractions.Tests/CoreAbstractions.Tests.xproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 14.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | 6c4d981b-777e-4821-a72c-0aac8ae6d525 10 | CoreAbstractions.Tests 11 | .\obj 12 | .\bin\ 13 | v4.5.2 14 | 15 | 16 | 2.0 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Test/CoreAbstractions.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyConfiguration("")] 9 | [assembly: AssemblyCompany("")] 10 | [assembly: AssemblyProduct("CoreAbstractions.Tests")] 11 | [assembly: AssemblyTrademark("")] 12 | 13 | // Setting ComVisible to false makes the types in this assembly not visible 14 | // to COM components. If you need to access a type in this assembly from 15 | // COM, set the ComVisible attribute to true on that type. 16 | [assembly: ComVisible(false)] 17 | 18 | // The following GUID is for the ID of the typelib if this project is exposed to COM 19 | [assembly: Guid("6c4d981b-777e-4821-a72c-0aac8ae6d525")] 20 | -------------------------------------------------------------------------------- /Test/CoreAbstractions.Tests/Utilities/UserUtilTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CoreAbstractions.Utilities; 3 | using Xunit; 4 | 5 | namespace CoreAbstractions.Tests.Utilities 6 | { 7 | public class UserUtilTests 8 | { 9 | [Fact] 10 | public void GetFullName_InvalidArguments_ThrowsArgumentException() 11 | { 12 | Assert.Throws(() => UserUtil.GetFullName("", "")); 13 | Assert.Throws(() => UserUtil.GetFullName("Mary", "")); 14 | } 15 | 16 | [Fact] 17 | public void GetFullName_ValidInputs_ReturnsCorrectResult_Example1() 18 | { 19 | Assert.Equal("Mary Jane", UserUtil.GetFullName(" Mary ", " Jane ")); 20 | } 21 | 22 | [Theory] 23 | [InlineData(" Mary", " Jane ", "Mary Jane")] 24 | [InlineData(" Bob", "Marley", "Bob Marley")] 25 | [InlineData(" Joe Hanson", " Lee ", "Joe Hanson Lee")] 26 | public void GetFullName_ValidInputs_ReturnsCorrectResult_Example2(string firstName, 27 | string lastName, string expectedFullName) 28 | { 29 | string fullNameResult = UserUtil.GetFullName(firstName, lastName); 30 | 31 | Assert.Equal(expectedFullName, fullNameResult); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Test/CoreAbstractions.Tests/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0-*", 3 | 4 | "testRunner": "xunit", 5 | 6 | "dependencies": { 7 | "ServerAbstractions": { 8 | "target": "project" 9 | }, 10 | "TestsCore": { 11 | "target": "project" 12 | } 13 | }, 14 | 15 | "frameworks": { 16 | "netcoreapp1.1": { 17 | "dependencies": { 18 | "Microsoft.NETCore.App": { 19 | "version": "1.1.0", 20 | "type": "platform" 21 | } 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Test/ServerComponents.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyConfiguration("")] 9 | [assembly: AssemblyCompany("")] 10 | [assembly: AssemblyProduct("ServerComponents.Tests")] 11 | [assembly: AssemblyTrademark("")] 12 | 13 | // Setting ComVisible to false makes the types in this assembly not visible 14 | // to COM components. If you need to access a type in this assembly from 15 | // COM, set the ComVisible attribute to true on that type. 16 | [assembly: ComVisible(false)] 17 | 18 | // The following GUID is for the ID of the typelib if this project is exposed to COM 19 | [assembly: Guid("56c92670-2547-4b82-97fa-c9677646836c")] 20 | -------------------------------------------------------------------------------- /Test/ServerComponents.Tests/ServerComponents.Tests.xproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 14.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | 56c92670-2547-4b82-97fa-c9677646836c 10 | ServerComponents.Tests 11 | .\obj 12 | .\bin\ 13 | v4.5.2 14 | 15 | 16 | 2.0 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Test/ServerComponents.Tests/Services/ReportServiceTests.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Moq; 3 | using ServerAbstractions; 4 | using ServerAbstractions.Services; 5 | using ServerComponents.Services; 6 | using TestsCore; 7 | 8 | namespace ServerComponents.Tests.Services 9 | { 10 | public class ReportServiceTests 11 | { 12 | private TestingObject GetTestingObject() 13 | { 14 | var testingObject = new TestingObject(); 15 | testingObject.AddDependency(new Mock(MockBehavior.Strict)); 16 | testingObject.AddDependency(new Mock(MockBehavior.Strict)); 17 | return testingObject; 18 | } 19 | 20 | public async Task GetAsync_InvalidArgument_ThrowsArgumentException() 21 | { 22 | 23 | } 24 | 25 | public async Task GetAsync_ReportNotFound_ThrowsException() 26 | { 27 | 28 | } 29 | 30 | public async Task GetAsync_ExpiredReport_ThrowsException() 31 | { 32 | 33 | } 34 | 35 | public async Task GetAsync_OwnerNotFound_ThrowsException() 36 | { 37 | 38 | } 39 | 40 | public async Task GetAsync_OwnerDisabled_ThrowsException() 41 | { 42 | 43 | } 44 | 45 | public async Task GetAsync_Success_Updated_ReturnsCorrectResult() 46 | { 47 | 48 | } 49 | 50 | public async Task GetAsync_Success_NotUpdated_ReturnsCorrectResult() 51 | { 52 | 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Test/ServerComponents.Tests/Services/UserServiceTests.cs: -------------------------------------------------------------------------------- 1 | using ServerComponents.Services; 2 | using System; 3 | using System.Threading.Tasks; 4 | using CoreAbstractions.Entities; 5 | using Moq; 6 | using ServerAbstractions; 7 | using ServerAbstractions.SqlEntities; 8 | using TestsCore; 9 | using Xunit; 10 | 11 | namespace ServerComponents.Tests.Services 12 | { 13 | public class UserServiceTests 14 | { 15 | private TestingObject GetTestingObject() 16 | { 17 | var testingObject = new TestingObject(); 18 | testingObject.AddDependency(new Mock(MockBehavior.Strict)); 19 | return testingObject; 20 | } 21 | 22 | [Fact] 23 | public async Task GetAsync_InvalidArgument_ThrowsArgumentException() 24 | { 25 | TestingObject testingObject = this.GetTestingObject(); 26 | 27 | UserService userService = testingObject.GetResolvedTestingObject(); 28 | 29 | await Assert.ThrowsAsync(async () => 30 | await userService.GetAsync(Guid.Empty)); 31 | } 32 | 33 | [Fact] 34 | public async Task GetAsync_UserNotFound_ThrowsException() 35 | { 36 | TestingObject testingObject = this.GetTestingObject(); 37 | 38 | Guid userIdArg = Guid.NewGuid(); 39 | 40 | var mockDbContext = testingObject.GetDependency>(); 41 | mockDbContext 42 | .Setup(dbc => dbc.FindSingleAsync(It.Is(id => id == userIdArg))) 43 | .ReturnsAsync(null); 44 | 45 | UserService userService = testingObject.GetResolvedTestingObject(); 46 | await Assert.ThrowsAsync(async () 47 | => await userService.GetAsync(userIdArg)); 48 | } 49 | 50 | [Fact] 51 | public async Task GetAsync_Success_ReturnsCorrectResult() 52 | { 53 | TestingObject testingObject = this.GetTestingObject(); 54 | 55 | Guid userIdArg = Guid.NewGuid(); 56 | 57 | var mockDbContext = testingObject.GetDependency>(); 58 | 59 | var userSql = new UserSql 60 | { 61 | FirstName = "Mary", 62 | LastName = "Jane" 63 | }; 64 | mockDbContext 65 | .Setup(dbc => dbc.FindSingleAsync(It.Is(id => id == userIdArg))) 66 | .ReturnsAsync(userSql); 67 | 68 | UserService userService = testingObject.GetResolvedTestingObject(); 69 | User result = await userService.GetAsync(userIdArg); 70 | 71 | Assert.Equal(userSql.FirstName, result.FirstName); 72 | Assert.Equal(userSql.LastName, result.LastName); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Test/ServerComponents.Tests/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0-*", 3 | 4 | "testRunner": "xunit", 5 | 6 | "dependencies": { 7 | "ServerAbstractions": { 8 | "target": "project" 9 | }, 10 | "ServerComponents": { 11 | "target": "project" 12 | }, 13 | "TestsCore": { 14 | "target": "project" 15 | } 16 | }, 17 | 18 | "frameworks": { 19 | "netcoreapp1.1": { 20 | "dependencies": { 21 | "Microsoft.NETCore.App": { 22 | "version": "1.1.0", 23 | "type": "platform" 24 | } 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Test/TestsCore/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyConfiguration("")] 9 | [assembly: AssemblyCompany("")] 10 | [assembly: AssemblyProduct("TestsCore")] 11 | [assembly: AssemblyTrademark("")] 12 | 13 | // Setting ComVisible to false makes the types in this assembly not visible 14 | // to COM components. If you need to access a type in this assembly from 15 | // COM, set the ComVisible attribute to true on that type. 16 | [assembly: ComVisible(false)] 17 | 18 | // The following GUID is for the ID of the typelib if this project is exposed to COM 19 | [assembly: Guid("1354e5f2-3b06-472d-967e-8a392aec7f80")] 20 | -------------------------------------------------------------------------------- /Test/TestsCore/TestingObject.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reflection; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Moq; 6 | 7 | namespace TestsCore 8 | { 9 | public class TestingObject where T : class 10 | { 11 | private Dictionary dependencyMap { get; } = new Dictionary(); 12 | 13 | public void AddDependency(TDependency dependency) 14 | { 15 | this.dependencyMap.Add(typeof(TDependency), dependency); 16 | } 17 | 18 | public TDependency GetDependency() where TDependency : class 19 | { 20 | Type type = typeof(TDependency); 21 | 22 | object dependency; 23 | if (!this.dependencyMap.TryGetValue(type, out dependency)) 24 | { 25 | throw new Exception($"Testing object doesn't contain dependency of type {type}."); 26 | } 27 | 28 | return dependency as TDependency; 29 | } 30 | 31 | public T GetResolvedTestingObject() 32 | { 33 | IServiceCollection serviceCollection = new ServiceCollection(); 34 | 35 | foreach (var dependency in this.dependencyMap) 36 | { 37 | TypeInfo typeInfo = dependency.Key.GetTypeInfo(); 38 | 39 | if (typeInfo.IsGenericType && typeInfo.GetGenericTypeDefinition() == typeof(Mock<>)) 40 | { 41 | PropertyInfo propertyInfo = dependency.Key.GetProperty("Object", 42 | BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly); 43 | object value = propertyInfo.GetValue(dependency.Value); 44 | 45 | serviceCollection.AddSingleton(dependency.Key.GenericTypeArguments[0], value); 46 | } 47 | else 48 | { 49 | serviceCollection.AddSingleton(dependency.Key, dependency.Value); 50 | } 51 | } 52 | 53 | IServiceProvider serviceProvider = serviceCollection.BuildServiceProvider(); 54 | return ActivatorUtilities.CreateInstance(serviceProvider); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Test/TestsCore/TestsCore.xproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 14.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | 10 | 1354e5f2-3b06-472d-967e-8a392aec7f80 11 | TestsCore 12 | .\obj 13 | .\bin\ 14 | v4.5.2 15 | 16 | 17 | 18 | 2.0 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Test/TestsCore/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0-*", 3 | 4 | "testRunner": "xunit", 5 | 6 | "dependencies": { 7 | "xunit": "2.2.0-beta2-build3300", 8 | "dotnet-test-xunit": "2.2.0-preview2-build1029", 9 | "ServerAbstractions": { 10 | "target": "project" 11 | }, 12 | "Microsoft.Extensions.DependencyInjection": "1.1.0", 13 | "Moq": "4.6.38-alpha" 14 | }, 15 | 16 | "frameworks": { 17 | "netcoreapp1.1": { 18 | "dependencies": { 19 | "Microsoft.NETCore.App": { 20 | "version": "1.1.0", 21 | "type": "platform" 22 | } 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /UnitTestingExample.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{89E66243-7329-4FDA-A5DC-E8A42D0F4EC1}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{4465A5AF-30FD-4A39-9DAB-903AA132AED3}" 9 | ProjectSection(SolutionItems) = preProject 10 | global.json = global.json 11 | EndProjectSection 12 | EndProject 13 | Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ServerComponents.Tests", "Test\ServerComponents.Tests\ServerComponents.Tests.xproj", "{56C92670-2547-4B82-97FA-C9677646836C}" 14 | EndProject 15 | Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ServerComponents", "src\ServerComponents\ServerComponents.xproj", "{B23AAF4C-5C7A-41AC-9CBC-736F8291B93F}" 16 | EndProject 17 | Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ServerAbstractions", "src\ServerAbstractions\ServerAbstractions.xproj", "{693B87B0-F82C-4B7E-9DC7-9DC849DAFD77}" 18 | EndProject 19 | Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "CoreAbstractions", "src\CoreAbstractions\CoreAbstractions.xproj", "{4B4BC71B-C727-4C81-9FBE-802606608447}" 20 | EndProject 21 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{DD33F128-2475-4258-9D6F-67AC09E19B3C}" 22 | EndProject 23 | Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "CoreAbstractions.Tests", "Test\CoreAbstractions.Tests\CoreAbstractions.Tests.xproj", "{6C4D981B-777E-4821-A72C-0AAC8AE6D525}" 24 | EndProject 25 | Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "TestsCore", "Test\TestsCore\TestsCore.xproj", "{1354E5F2-3B06-472D-967E-8A392AEC7F80}" 26 | EndProject 27 | Global 28 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 29 | Debug|Any CPU = Debug|Any CPU 30 | Release|Any CPU = Release|Any CPU 31 | EndGlobalSection 32 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 33 | {56C92670-2547-4B82-97FA-C9677646836C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 34 | {56C92670-2547-4B82-97FA-C9677646836C}.Debug|Any CPU.Build.0 = Debug|Any CPU 35 | {56C92670-2547-4B82-97FA-C9677646836C}.Release|Any CPU.ActiveCfg = Release|Any CPU 36 | {56C92670-2547-4B82-97FA-C9677646836C}.Release|Any CPU.Build.0 = Release|Any CPU 37 | {B23AAF4C-5C7A-41AC-9CBC-736F8291B93F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 38 | {B23AAF4C-5C7A-41AC-9CBC-736F8291B93F}.Debug|Any CPU.Build.0 = Debug|Any CPU 39 | {B23AAF4C-5C7A-41AC-9CBC-736F8291B93F}.Release|Any CPU.ActiveCfg = Release|Any CPU 40 | {B23AAF4C-5C7A-41AC-9CBC-736F8291B93F}.Release|Any CPU.Build.0 = Release|Any CPU 41 | {693B87B0-F82C-4B7E-9DC7-9DC849DAFD77}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 42 | {693B87B0-F82C-4B7E-9DC7-9DC849DAFD77}.Debug|Any CPU.Build.0 = Debug|Any CPU 43 | {693B87B0-F82C-4B7E-9DC7-9DC849DAFD77}.Release|Any CPU.ActiveCfg = Release|Any CPU 44 | {693B87B0-F82C-4B7E-9DC7-9DC849DAFD77}.Release|Any CPU.Build.0 = Release|Any CPU 45 | {4B4BC71B-C727-4C81-9FBE-802606608447}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 46 | {4B4BC71B-C727-4C81-9FBE-802606608447}.Debug|Any CPU.Build.0 = Debug|Any CPU 47 | {4B4BC71B-C727-4C81-9FBE-802606608447}.Release|Any CPU.ActiveCfg = Release|Any CPU 48 | {4B4BC71B-C727-4C81-9FBE-802606608447}.Release|Any CPU.Build.0 = Release|Any CPU 49 | {6C4D981B-777E-4821-A72C-0AAC8AE6D525}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 50 | {6C4D981B-777E-4821-A72C-0AAC8AE6D525}.Debug|Any CPU.Build.0 = Debug|Any CPU 51 | {6C4D981B-777E-4821-A72C-0AAC8AE6D525}.Release|Any CPU.ActiveCfg = Release|Any CPU 52 | {6C4D981B-777E-4821-A72C-0AAC8AE6D525}.Release|Any CPU.Build.0 = Release|Any CPU 53 | {1354E5F2-3B06-472D-967E-8A392AEC7F80}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 54 | {1354E5F2-3B06-472D-967E-8A392AEC7F80}.Debug|Any CPU.Build.0 = Debug|Any CPU 55 | {1354E5F2-3B06-472D-967E-8A392AEC7F80}.Release|Any CPU.ActiveCfg = Release|Any CPU 56 | {1354E5F2-3B06-472D-967E-8A392AEC7F80}.Release|Any CPU.Build.0 = Release|Any CPU 57 | EndGlobalSection 58 | GlobalSection(SolutionProperties) = preSolution 59 | HideSolutionNode = FALSE 60 | EndGlobalSection 61 | GlobalSection(NestedProjects) = preSolution 62 | {56C92670-2547-4B82-97FA-C9677646836C} = {DD33F128-2475-4258-9D6F-67AC09E19B3C} 63 | {B23AAF4C-5C7A-41AC-9CBC-736F8291B93F} = {89E66243-7329-4FDA-A5DC-E8A42D0F4EC1} 64 | {693B87B0-F82C-4B7E-9DC7-9DC849DAFD77} = {89E66243-7329-4FDA-A5DC-E8A42D0F4EC1} 65 | {4B4BC71B-C727-4C81-9FBE-802606608447} = {89E66243-7329-4FDA-A5DC-E8A42D0F4EC1} 66 | {6C4D981B-777E-4821-A72C-0AAC8AE6D525} = {DD33F128-2475-4258-9D6F-67AC09E19B3C} 67 | {1354E5F2-3B06-472D-967E-8A392AEC7F80} = {DD33F128-2475-4258-9D6F-67AC09E19B3C} 68 | EndGlobalSection 69 | EndGlobal 70 | -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "projects": [ "src", "test", "." ], 3 | "sdk": { 4 | "version": "1.0.0-preview2-003131" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/CoreAbstractions/CoreAbstractions.xproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 14.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | 10 | 4b4bc71b-c727-4c81-9fbe-802606608447 11 | CoreAbstractions 12 | .\obj 13 | .\bin\ 14 | v4.5.2 15 | 16 | 17 | 18 | 2.0 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/CoreAbstractions/Entities/ReportMetadata.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CoreAbstractions.Entities 4 | { 5 | public class ReportMetadata 6 | { 7 | public Guid Id { get; set; } 8 | public string Title { get; set; } 9 | public DateTime Created { get; set; } 10 | public DateTime? LastUpdated { get; set; } 11 | public string AuthorFullName { get; set; } 12 | public Guid? LastRevisionById { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/CoreAbstractions/Entities/User.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CoreAbstractions.Entities 4 | { 5 | public class User 6 | { 7 | public Guid Id { get; set; } 8 | public string FirstName { get; set; } 9 | public string LastName { get; set; } 10 | public bool Enabled { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/CoreAbstractions/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyConfiguration("")] 9 | [assembly: AssemblyCompany("")] 10 | [assembly: AssemblyProduct("CoreAbstractions")] 11 | [assembly: AssemblyTrademark("")] 12 | 13 | // Setting ComVisible to false makes the types in this assembly not visible 14 | // to COM components. If you need to access a type in this assembly from 15 | // COM, set the ComVisible attribute to true on that type. 16 | [assembly: ComVisible(false)] 17 | 18 | // The following GUID is for the ID of the typelib if this project is exposed to COM 19 | [assembly: Guid("4b4bc71b-c727-4c81-9fbe-802606608447")] 20 | -------------------------------------------------------------------------------- /src/CoreAbstractions/Utilities/UserUtil.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CoreAbstractions.Entities; 3 | 4 | namespace CoreAbstractions.Utilities 5 | { 6 | public static class UserUtil 7 | { 8 | public static string GetFullName(string firstName, string lastName) 9 | { 10 | if (string.IsNullOrWhiteSpace(firstName)) 11 | { 12 | throw new ArgumentException("First name must be provided."); 13 | } 14 | if (string.IsNullOrWhiteSpace(lastName)) 15 | { 16 | throw new ArgumentException("Last name must be provided."); 17 | } 18 | 19 | return $"{firstName.Trim()} {lastName.Trim()}".Trim(); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/CoreAbstractions/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0-*", 3 | 4 | "dependencies": { 5 | "NETStandard.Library": "1.6.0" 6 | }, 7 | 8 | "frameworks": { 9 | "netstandard1.6": { 10 | "imports": "dnxcore50" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/ServerAbstractions/IDatabaseContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace ServerAbstractions 5 | { 6 | public interface IDatabaseContext 7 | { 8 | Task FindSingleAsync(Guid id); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/ServerAbstractions/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyConfiguration("")] 9 | [assembly: AssemblyCompany("")] 10 | [assembly: AssemblyProduct("ServerAbstractions")] 11 | [assembly: AssemblyTrademark("")] 12 | 13 | // Setting ComVisible to false makes the types in this assembly not visible 14 | // to COM components. If you need to access a type in this assembly from 15 | // COM, set the ComVisible attribute to true on that type. 16 | [assembly: ComVisible(false)] 17 | 18 | // The following GUID is for the ID of the typelib if this project is exposed to COM 19 | [assembly: Guid("693b87b0-f82c-4b7e-9dc7-9dc849dafd77")] 20 | -------------------------------------------------------------------------------- /src/ServerAbstractions/ServerAbstractions.xproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 14.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | 10 | 693b87b0-f82c-4b7e-9dc7-9dc849dafd77 11 | ServerAbstractions 12 | .\obj 13 | .\bin\ 14 | v4.5.2 15 | 16 | 17 | 18 | 2.0 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/ServerAbstractions/Services/IReportService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using CoreAbstractions.Entities; 4 | 5 | namespace ServerAbstractions.Services 6 | { 7 | public interface IReportService 8 | { 9 | Task GetAsync(Guid reportId); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/ServerAbstractions/Services/IUserService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using CoreAbstractions.Entities; 4 | 5 | namespace ServerAbstractions.Services 6 | { 7 | public interface IUserService 8 | { 9 | Task GetAsync(Guid userId); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/ServerAbstractions/SqlEntities/ReportMetadataSql.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ServerAbstractions.SqlEntities 4 | { 5 | public class ReportMetadataSql 6 | { 7 | public Guid Id { get; set; } 8 | public string Title { get; set; } 9 | public DateTime Created { get; set; } 10 | public DateTime? LastUpdated { get; set; } 11 | public Guid OwnerId { get; set; } 12 | public Guid? LastRevisionById { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/ServerAbstractions/SqlEntities/UserSql.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CoreAbstractions.Entities; 3 | 4 | namespace ServerAbstractions.SqlEntities 5 | { 6 | public class UserSql 7 | { 8 | public Guid Id { get; set; } 9 | public string FirstName { get; set; } 10 | public string LastName { get; set; } 11 | public bool Enabled { get; set; } 12 | 13 | public static User ToEntity(UserSql sql) 14 | { 15 | return new User 16 | { 17 | Id = sql.Id, 18 | FirstName = sql.FirstName, 19 | LastName = sql.LastName, 20 | Enabled = sql.Enabled 21 | }; 22 | } 23 | } 24 | } 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/ServerAbstractions/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0-*", 3 | 4 | "dependencies": { 5 | "CoreAbstractions": { 6 | "target": "project" 7 | }, 8 | "NETStandard.Library": "1.6.0" 9 | }, 10 | 11 | "frameworks": { 12 | "netstandard1.6": { 13 | "imports": "dnxcore50" 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/ServerComponents/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyConfiguration("")] 9 | [assembly: AssemblyCompany("")] 10 | [assembly: AssemblyProduct("ServerComponents")] 11 | [assembly: AssemblyTrademark("")] 12 | 13 | // Setting ComVisible to false makes the types in this assembly not visible 14 | // to COM components. If you need to access a type in this assembly from 15 | // COM, set the ComVisible attribute to true on that type. 16 | [assembly: ComVisible(false)] 17 | 18 | // The following GUID is for the ID of the typelib if this project is exposed to COM 19 | [assembly: Guid("b23aaf4c-5c7a-41ac-9cbc-736f8291b93f")] 20 | -------------------------------------------------------------------------------- /src/ServerComponents/ServerComponents.xproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 14.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | 10 | b23aaf4c-5c7a-41ac-9cbc-736f8291b93f 11 | ServerComponents 12 | .\obj 13 | .\bin\ 14 | v4.5.2 15 | 16 | 17 | 18 | 2.0 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/ServerComponents/Services/ReportService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using CoreAbstractions.Entities; 4 | using CoreAbstractions.Utilities; 5 | using ServerAbstractions; 6 | using ServerAbstractions.Services; 7 | using ServerAbstractions.SqlEntities; 8 | 9 | namespace ServerComponents.Services 10 | { 11 | public class ReportService : IReportService 12 | { 13 | private IUserService UserService { get; } 14 | private IDatabaseContext DbContext { get; } 15 | 16 | public ReportService( 17 | IUserService userService, 18 | IDatabaseContext dbContext) 19 | { 20 | this.UserService = userService; 21 | this.DbContext = dbContext; 22 | } 23 | 24 | public async Task GetAsync(Guid reportId) 25 | { 26 | if (reportId == Guid.Empty) 27 | { 28 | throw new ArgumentException("Report id must be provided", nameof(reportId)); 29 | } 30 | 31 | ReportMetadataSql reportSql = await this.DbContext.FindSingleAsync(reportId); 32 | if (reportSql == null) 33 | { 34 | throw new Exception($"Report '{reportId}' was not found."); 35 | } 36 | 37 | 38 | // if report is older than 3 years, dont return it 39 | if (reportSql.Created.AddYears(3) < DateTime.UtcNow) 40 | { 41 | throw new Exception($"Report '{reportId}' is too old and will not be retrieved."); 42 | } 43 | 44 | UserSql ownerSql = await this.DbContext.FindSingleAsync(reportSql.OwnerId); 45 | if (ownerSql == null) 46 | { 47 | throw new Exception($"User '{reportSql.OwnerId}' not found."); 48 | } 49 | 50 | 51 | // if owner of report is disabled, dont return it 52 | if (!ownerSql.Enabled) 53 | { 54 | throw new Exception($"Report '{reportId}' is owned by a disabled user and will not be retrieved."); 55 | } 56 | 57 | User owner = UserSql.ToEntity(ownerSql); 58 | string authorFullName = UserUtil.GetFullName(owner.FirstName, owner.LastName); 59 | 60 | // modify title based on whether it's been updated 61 | if (reportSql.LastUpdated.HasValue) 62 | { 63 | reportSql.Title += " (Revision)"; 64 | } 65 | else 66 | { 67 | reportSql.Title += " (Original)"; 68 | } 69 | 70 | return new ReportMetadata 71 | { 72 | Id = reportSql.Id, 73 | LastUpdated = reportSql.LastUpdated, 74 | LastRevisionById = reportSql.LastRevisionById, 75 | Title = reportSql.Title, 76 | Created = reportSql.Created, 77 | AuthorFullName = authorFullName 78 | }; 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/ServerComponents/Services/UserService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using CoreAbstractions.Entities; 4 | using ServerAbstractions; 5 | using ServerAbstractions.Services; 6 | using ServerAbstractions.SqlEntities; 7 | 8 | namespace ServerComponents.Services 9 | { 10 | public class UserService : IUserService 11 | { 12 | private IDatabaseContext DbContext { get; } 13 | 14 | public UserService(IDatabaseContext dbContext) 15 | { 16 | this.DbContext = dbContext; 17 | } 18 | 19 | public async Task GetAsync(Guid userId) 20 | { 21 | if (userId == Guid.Empty) 22 | { 23 | throw new ArgumentException("Must specify a user id.", nameof(userId)); 24 | } 25 | 26 | UserSql userSql = await this.DbContext.FindSingleAsync(userId); 27 | 28 | if (userSql == null) 29 | { 30 | throw new Exception($"User '{userId}' was not found."); 31 | } 32 | 33 | return UserSql.ToEntity(userSql); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/ServerComponents/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0-*", 3 | 4 | "dependencies": { 5 | "NETStandard.Library": "1.6.0", 6 | "ServerAbstractions": "1.0.0-*" 7 | }, 8 | 9 | "frameworks": { 10 | "netstandard1.6": { 11 | "imports": "dnxcore50" 12 | } 13 | } 14 | } 15 | --------------------------------------------------------------------------------