├── .gitignore ├── EntityFrameworkMock.sln ├── LICENSE ├── README.md ├── src ├── EntityFrameworkMock.Moq │ ├── DbContextMock.cs │ ├── DbSetMock.cs │ ├── EntityFrameworkMock.Moq.csproj │ └── _Visibility.cs ├── EntityFrameworkMock.NSubstitute │ ├── DbContextMock.cs │ ├── DbSetMock.cs │ ├── EntityFrameworkMock.NSubstitute.csproj │ └── _Visibility.cs └── EntityFrameworkMock.Shared │ ├── AttributeBasedKeyFactoryBuilder.cs │ ├── DbAsyncEnumerable.cs │ ├── DbAsyncEnumerator.cs │ ├── DbAsyncQueryProvider.cs │ ├── DbSetBackingStore.cs │ ├── EntityFrameworkMock.Shared.csproj │ ├── IDbSetMock.cs │ ├── IKeyFactoryBuilder.cs │ ├── KeyContext.cs │ ├── KeyFactoryNormalizer.cs │ ├── SavedChangesEventArgs.cs │ └── SqlExceptionCreator.cs └── tests ├── EntityFrameworkMock.Moq.Tests ├── AutoMapperRelatedTests.cs ├── DbContextMockTests.cs ├── DbSetMockTests.cs ├── EntityFrameworkMock.Moq.Tests.csproj └── Models │ ├── GeneratedGuidKeyModel.cs │ ├── GeneratedKeyModel.cs │ ├── IntKeyModel.cs │ ├── NoKeyModel.cs │ ├── Order.cs │ └── User.cs ├── EntityFrameworkMock.NSubstitute.Tests ├── AutoMapperRelatedTests.cs ├── DbContextMockTests.cs ├── DbSetMockTests.cs ├── EntityFrameworkMock.NSubstitute.Tests.csproj └── Models │ ├── GeneratedGuidKeyModel.cs │ ├── GeneratedKeyModel.cs │ ├── IntKeyModel.cs │ ├── NoKeyModel.cs │ ├── Order.cs │ └── User.cs └── EntityFrameworkMock.Shared.Tests ├── AttributeBasedKeyFactoryBuilderTests.cs ├── EntityFrameworkMock.Shared.Tests.csproj ├── KeyContextTests.cs ├── Models ├── TenantUser.cs └── User.cs └── SqlExceptionCreatorTests.cs /.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 ignorable 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 | orleans.codegen.cs 203 | 204 | # Since there are multiple workflows, uncomment next line to ignore bower_components 205 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 206 | #bower_components/ 207 | 208 | # RIA/Silverlight projects 209 | Generated_Code/ 210 | 211 | # Backup & report files from converting an old project file 212 | # to a newer Visual Studio version. Backup files are not needed, 213 | # because we have git ;-) 214 | _UpgradeReport_Files/ 215 | Backup*/ 216 | UpgradeLog*.XML 217 | UpgradeLog*.htm 218 | 219 | # SQL Server files 220 | *.mdf 221 | *.ldf 222 | *.ndf 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 | node_modules/ 238 | 239 | # Typescript v1 declaration files 240 | typings/ 241 | 242 | # Visual Studio 6 build log 243 | *.plg 244 | 245 | # Visual Studio 6 workspace options file 246 | *.opt 247 | 248 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 249 | *.vbw 250 | 251 | # Visual Studio LightSwitch build output 252 | **/*.HTMLClient/GeneratedArtifacts 253 | **/*.DesktopClient/GeneratedArtifacts 254 | **/*.DesktopClient/ModelManifest.xml 255 | **/*.Server/GeneratedArtifacts 256 | **/*.Server/ModelManifest.xml 257 | _Pvt_Extensions 258 | 259 | # Paket dependency manager 260 | .paket/paket.exe 261 | paket-files/ 262 | 263 | # FAKE - F# Make 264 | .fake/ 265 | 266 | # JetBrains Rider 267 | .idea/ 268 | *.sln.iml 269 | 270 | # CodeRush 271 | .cr/ 272 | 273 | # Python Tools for Visual Studio (PTVS) 274 | __pycache__/ 275 | *.pyc 276 | 277 | # Cake - Uncomment if you are using it 278 | # tools/** 279 | # !tools/packages.config 280 | 281 | # Telerik's JustMock configuration file 282 | *.jmconfig 283 | 284 | # BizTalk build output 285 | *.btp.cs 286 | *.btm.cs 287 | *.odx.cs 288 | *.xsd.cs 289 | -------------------------------------------------------------------------------- /EntityFrameworkMock.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27004.2008 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EntityFrameworkMock.Moq", "src\EntityFrameworkMock.Moq\EntityFrameworkMock.Moq.csproj", "{84B1E11A-C0A1-42F5-9F7A-0C55ED2C7D9C}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EntityFrameworkMock.Moq.Tests", "tests\EntityFrameworkMock.Moq.Tests\EntityFrameworkMock.Moq.Tests.csproj", "{28EB943C-8D7B-431C-9E44-E849043FF0D5}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{91C5E8E2-DEB7-4F2E-BBAE-2962B44F7C31}" 11 | ProjectSection(SolutionItems) = preProject 12 | LICENSE = LICENSE 13 | README.md = README.md 14 | EndProjectSection 15 | EndProject 16 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Moq", "Moq", "{BCEBE803-A099-48BD-AF91-FFD4FA2113FF}" 17 | EndProject 18 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "@Shared", "@Shared", "{E5531D22-3250-4A95-8157-D292B41CD0EE}" 19 | EndProject 20 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EntityFrameworkMock.Shared", "src\EntityFrameworkMock.Shared\EntityFrameworkMock.Shared.csproj", "{29D0B720-5189-4A96-92E0-C69AC791C0A0}" 21 | EndProject 22 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EntityFrameworkMock.Shared.Tests", "tests\EntityFrameworkMock.Shared.Tests\EntityFrameworkMock.Shared.Tests.csproj", "{9FF1D62C-2103-41BA-98BC-FDB3C13004C8}" 23 | EndProject 24 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "NSubstitute", "NSubstitute", "{6320D96B-E5E7-4183-964E-3A9CC7BAD043}" 25 | EndProject 26 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EntityFrameworkMock.NSubstitute.Tests", "tests\EntityFrameworkMock.NSubstitute.Tests\EntityFrameworkMock.NSubstitute.Tests.csproj", "{8E8FB4F1-578A-4DC2-B113-97B87C0E3501}" 27 | EndProject 28 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EntityFrameworkMock.NSubstitute", "src\EntityFrameworkMock.NSubstitute\EntityFrameworkMock.NSubstitute.csproj", "{2C669CD3-5428-48EE-A809-53E5B7E4E5A6}" 29 | EndProject 30 | Global 31 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 32 | Debug|Any CPU = Debug|Any CPU 33 | Release|Any CPU = Release|Any CPU 34 | EndGlobalSection 35 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 36 | {84B1E11A-C0A1-42F5-9F7A-0C55ED2C7D9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {84B1E11A-C0A1-42F5-9F7A-0C55ED2C7D9C}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {84B1E11A-C0A1-42F5-9F7A-0C55ED2C7D9C}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {84B1E11A-C0A1-42F5-9F7A-0C55ED2C7D9C}.Release|Any CPU.Build.0 = Release|Any CPU 40 | {28EB943C-8D7B-431C-9E44-E849043FF0D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {28EB943C-8D7B-431C-9E44-E849043FF0D5}.Debug|Any CPU.Build.0 = Debug|Any CPU 42 | {28EB943C-8D7B-431C-9E44-E849043FF0D5}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {28EB943C-8D7B-431C-9E44-E849043FF0D5}.Release|Any CPU.Build.0 = Release|Any CPU 44 | {29D0B720-5189-4A96-92E0-C69AC791C0A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 45 | {29D0B720-5189-4A96-92E0-C69AC791C0A0}.Debug|Any CPU.Build.0 = Debug|Any CPU 46 | {29D0B720-5189-4A96-92E0-C69AC791C0A0}.Release|Any CPU.ActiveCfg = Release|Any CPU 47 | {29D0B720-5189-4A96-92E0-C69AC791C0A0}.Release|Any CPU.Build.0 = Release|Any CPU 48 | {9FF1D62C-2103-41BA-98BC-FDB3C13004C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 49 | {9FF1D62C-2103-41BA-98BC-FDB3C13004C8}.Debug|Any CPU.Build.0 = Debug|Any CPU 50 | {9FF1D62C-2103-41BA-98BC-FDB3C13004C8}.Release|Any CPU.ActiveCfg = Release|Any CPU 51 | {9FF1D62C-2103-41BA-98BC-FDB3C13004C8}.Release|Any CPU.Build.0 = Release|Any CPU 52 | {8E8FB4F1-578A-4DC2-B113-97B87C0E3501}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 53 | {8E8FB4F1-578A-4DC2-B113-97B87C0E3501}.Debug|Any CPU.Build.0 = Debug|Any CPU 54 | {8E8FB4F1-578A-4DC2-B113-97B87C0E3501}.Release|Any CPU.ActiveCfg = Release|Any CPU 55 | {8E8FB4F1-578A-4DC2-B113-97B87C0E3501}.Release|Any CPU.Build.0 = Release|Any CPU 56 | {2C669CD3-5428-48EE-A809-53E5B7E4E5A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 57 | {2C669CD3-5428-48EE-A809-53E5B7E4E5A6}.Debug|Any CPU.Build.0 = Debug|Any CPU 58 | {2C669CD3-5428-48EE-A809-53E5B7E4E5A6}.Release|Any CPU.ActiveCfg = Release|Any CPU 59 | {2C669CD3-5428-48EE-A809-53E5B7E4E5A6}.Release|Any CPU.Build.0 = Release|Any CPU 60 | EndGlobalSection 61 | GlobalSection(SolutionProperties) = preSolution 62 | HideSolutionNode = FALSE 63 | EndGlobalSection 64 | GlobalSection(NestedProjects) = preSolution 65 | {84B1E11A-C0A1-42F5-9F7A-0C55ED2C7D9C} = {BCEBE803-A099-48BD-AF91-FFD4FA2113FF} 66 | {28EB943C-8D7B-431C-9E44-E849043FF0D5} = {BCEBE803-A099-48BD-AF91-FFD4FA2113FF} 67 | {29D0B720-5189-4A96-92E0-C69AC791C0A0} = {E5531D22-3250-4A95-8157-D292B41CD0EE} 68 | {9FF1D62C-2103-41BA-98BC-FDB3C13004C8} = {E5531D22-3250-4A95-8157-D292B41CD0EE} 69 | {8E8FB4F1-578A-4DC2-B113-97B87C0E3501} = {6320D96B-E5E7-4183-964E-3A9CC7BAD043} 70 | {2C669CD3-5428-48EE-A809-53E5B7E4E5A6} = {6320D96B-E5E7-4183-964E-3A9CC7BAD043} 71 | EndGlobalSection 72 | GlobalSection(ExtensibilityGlobals) = postSolution 73 | SolutionGuid = {FB97BBBC-38CD-45AD-B426-9B8BE64C78AC} 74 | EndGlobalSection 75 | EndGlobal 76 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EntityFrameworkMock 2 | 3 | [![Build status](https://ci.appveyor.com/api/projects/status/5ung41elf64ahshg/branch/master?svg=true)](https://ci.appveyor.com/project/huysentruitw/entity-framework-mock/branch/master) 4 | 5 | Easy Mock wrapper for mocking EF6 DbContext and DbSet in your unit-tests. Integrates with Moq or NSubstitute. 6 | 7 | For mocking Entity Framework Core (EF Core) see 8 | 9 | ## Get it on NuGet 10 | 11 | ### Moq integration 12 | 13 | ```powershell 14 | PM> Install-Package EntityFrameworkMock.Moq 15 | ``` 16 | 17 | ### NSubstitute integration 18 | 19 | ```powershell 20 | PM> Install-Package EntityFrameworkMock.NSubstitute 21 | ``` 22 | 23 | ## Supports 24 | 25 | * In-memory storage of test data 26 | * Querying of in-memory test data (synchronous or asynchronous) 27 | * Tracking of updates, inserts and deletes of in-memory test data 28 | * Emulation of `SaveChanges` and `SaveChangesAsync` (only saves tracked changes to the mocked in-memory DbSet when one of these methods are called) 29 | * Auto-increment identity columns, annotated by the `[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]` attribute 30 | * Primary key on multiple columns, annotated by the `[Key, Column(Order = X)]` attributes 31 | * Throwing a `DbUpdateException` when inserting 2 or more entities with the same primary key while calling `SaveChanges` / `SaveChangesAsync` (emulating EF behavior) 32 | * Throwing a `DbUpdateConcurrencyException` when removing a model that no longer exists (emulating EF behavior) 33 | 34 | For the Moq version, you can use all known [Moq](https://github.com/Moq/moq4/wiki/Quickstart) features, since both `DbSetMock` and `DbContextMock` inherit from `Mock` and `Mock` respectively. 35 | 36 | ## Example usage 37 | 38 | ```csharp 39 | public class User 40 | { 41 | [Key, Column(Order = 0)] 42 | public Guid Id { get; set; } 43 | 44 | public string FullName { get; set; } 45 | } 46 | 47 | public class TestDbContext : DbContext 48 | { 49 | public TestDbContext(string connectionString) 50 | : base(connectionString) 51 | { 52 | } 53 | 54 | public virtual DbSet Users { get; set; } 55 | } 56 | 57 | [TestFixture] 58 | public class MyTests 59 | { 60 | var initialEntities = new[] 61 | { 62 | new User { Id = Guid.NewGuid(), FullName = "Eric Cartoon" }, 63 | new User { Id = Guid.NewGuid(), FullName = "Billy Jewel" }, 64 | }; 65 | 66 | var dbContextMock = new DbContextMock("fake connectionstring"); 67 | var usersDbSetMock = dbContextMock.CreateDbSetMock(x => x.Users, initialEntities); 68 | 69 | // Pass dbContextMock.Object to the class/method you want to test 70 | 71 | // Query dbContextMock.Object.Users to see if certain users were added or removed 72 | // or use Mock Verify functionality to verify if certain methods were called: usersDbSetMock.Verify(x => x.Add(...), Times.Once); 73 | } 74 | ``` 75 | -------------------------------------------------------------------------------- /src/EntityFrameworkMock.Moq/DbContextMock.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2019 Wouter Huysentruit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | using System; 18 | using System.Collections.Generic; 19 | using System.ComponentModel.DataAnnotations; 20 | using System.Data.Entity; 21 | using System.Linq; 22 | using System.Linq.Expressions; 23 | using System.Reflection; 24 | using System.Threading; 25 | using Moq; 26 | 27 | namespace EntityFrameworkMock 28 | { 29 | public class DbContextMock : Mock 30 | where TDbContext : DbContext 31 | { 32 | private readonly IKeyFactoryBuilder _keyFactoryBuilder; 33 | private readonly Dictionary _dbSetCache = new Dictionary(); 34 | 35 | public DbContextMock(params object[] args) 36 | : this(new AttributeBasedKeyFactoryBuilder(), args) 37 | { 38 | } 39 | 40 | private DbContextMock(IKeyFactoryBuilder keyFactoryBuilder, params object[] args) 41 | : base(args) 42 | { 43 | _keyFactoryBuilder = keyFactoryBuilder ?? throw new ArgumentNullException(nameof(keyFactoryBuilder)); 44 | Reset(); 45 | } 46 | 47 | public DbSetMock CreateDbSetMock(Expression>> dbSetSelector, IEnumerable initialEntities = null) 48 | where TEntity : class 49 | => CreateDbSetMock(dbSetSelector, _keyFactoryBuilder.BuildKeyFactory(), initialEntities); 50 | 51 | public DbSetMock CreateDbSetMock(Expression>> dbSetSelector, Func entityKeyFactory, IEnumerable initialEntities = null) 52 | where TEntity : class 53 | { 54 | if (dbSetSelector == null) throw new ArgumentNullException(nameof(dbSetSelector)); 55 | if (entityKeyFactory == null) throw new ArgumentNullException(nameof(entityKeyFactory)); 56 | 57 | var memberInfo = ((MemberExpression)dbSetSelector.Body).Member; 58 | if (_dbSetCache.ContainsKey(memberInfo)) throw new ArgumentException($"DbSetMock for {memberInfo.Name} already created", nameof(dbSetSelector)); 59 | var mock = new DbSetMock(initialEntities, entityKeyFactory); 60 | Setup(dbSetSelector).Returns(() => mock.Object); 61 | Setup(x => x.Set()).Returns(() => mock.Object); 62 | _dbSetCache.Add(memberInfo, mock); 63 | return mock; 64 | } 65 | 66 | public void Reset() 67 | { 68 | MockExtensions.Reset(this); 69 | _dbSetCache.Clear(); 70 | Setup(x => x.SaveChanges()).Returns(SaveChanges); 71 | Setup(x => x.SaveChangesAsync()).ReturnsAsync(SaveChanges); 72 | Setup(x => x.SaveChangesAsync(It.IsAny())).ReturnsAsync(SaveChanges); 73 | } 74 | 75 | // Facilitates unit-testing 76 | internal void RegisterDbSetMock(Expression>> dbSetSelector, IDbSetMock dbSet) 77 | where TEntity : class 78 | { 79 | var memberInfo = ((MemberExpression)dbSetSelector.Body).Member; 80 | _dbSetCache.Add(memberInfo, dbSet); 81 | } 82 | 83 | private int SaveChanges() => _dbSetCache.Values.Aggregate(0, (seed, dbSet) => seed + dbSet.SaveChanges()); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/EntityFrameworkMock.Moq/DbSetMock.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2019 Wouter Huysentruit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | using System; 18 | using System.Collections; 19 | using System.Collections.Generic; 20 | using System.Data.Entity; 21 | using System.Data.Entity.Infrastructure; 22 | using System.Linq; 23 | using System.Threading; 24 | using System.Threading.Tasks; 25 | using Moq; 26 | 27 | namespace EntityFrameworkMock 28 | { 29 | public class DbSetMock : Mock>, IDbSetMock 30 | where TEntity : class 31 | { 32 | private readonly DbSetBackingStore _store; 33 | 34 | public DbSetMock(IEnumerable initialEntities, Func keyFactory, bool asyncQuerySupport = true) 35 | { 36 | _store = new DbSetBackingStore(initialEntities, keyFactory); 37 | 38 | var data = _store.GetDataAsQueryable(); 39 | As>().Setup(x => x.Provider).Returns(asyncQuerySupport ? new DbAsyncQueryProvider(data.Provider) : data.Provider); 40 | As>().Setup(x => x.Expression).Returns(data.Expression); 41 | As>().Setup(x => x.ElementType).Returns(data.ElementType); 42 | As>().Setup(x => x.GetEnumerator()).Returns(() => data.GetEnumerator()); 43 | As().Setup(x => x.GetEnumerator()).Returns(() => data.GetEnumerator()); 44 | 45 | if (asyncQuerySupport) 46 | { 47 | As>().Setup(x => x.GetAsyncEnumerator()).Returns(() => new DbAsyncEnumerator(data.GetEnumerator())); 48 | } 49 | 50 | Setup(x => x.AsNoTracking()).Returns(() => Object); 51 | Setup(x => x.Include(It.IsAny())).Returns(() => Object); 52 | 53 | Setup(x => x.Add(It.IsAny())).Callback(_store.Add); 54 | Setup(x => x.AddRange(It.IsAny>())).Callback>(_store.Add); 55 | Setup(x => x.Remove(It.IsAny())).Callback(_store.Remove); 56 | Setup(x => x.RemoveRange(It.IsAny>())).Callback>(_store.Remove); 57 | 58 | Setup(x => x.Find(It.IsAny())).Returns(_store.Find); 59 | Setup(x => x.FindAsync(It.IsAny(), It.IsAny())).Returns((_, x) => Task.FromResult(_store.Find(x))); 60 | 61 | _store.UpdateSnapshot(); 62 | } 63 | 64 | public event EventHandler> SavedChanges; 65 | 66 | int IDbSetMock.SaveChanges() 67 | { 68 | var changes = _store.ApplyChanges(); 69 | SavedChanges?.Invoke(this, new SavedChangesEventArgs { UpdatedEntities = _store.GetUpdatedEntities() }); 70 | _store.UpdateSnapshot(); 71 | return changes; 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/EntityFrameworkMock.Moq/EntityFrameworkMock.Moq.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net472;netstandard2.1 5 | EntityFrameworkMock.Moq 6 | EntityFrameworkMock 7 | 1.0.0.0 8 | Wouter Huysentruit 9 | Wouter Huysentruit 10 | https://raw.githubusercontent.com/huysentruitw/entity-framework-mock/master/LICENSE 11 | https://github.com/huysentruitw/entity-framework-mock 12 | false 13 | Easy Mock wrapper for mocking EF6 DbContext and DbSet using Moq 14 | Copyright 2017-2019 Wouter Huysentruit 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/EntityFrameworkMock.Moq/_Visibility.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly:InternalsVisibleTo("EntityFrameworkMock.Moq.Tests")] -------------------------------------------------------------------------------- /src/EntityFrameworkMock.NSubstitute/DbContextMock.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2019 Paul Michaels, Wouter Huysentruit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | using System; 18 | using System.Collections.Generic; 19 | using System.ComponentModel.DataAnnotations; 20 | using System.Data.Entity; 21 | using System.Linq; 22 | using System.Linq.Expressions; 23 | using System.Reflection; 24 | using System.Threading; 25 | using NSubstitute; 26 | 27 | namespace EntityFrameworkMock.NSubstitute 28 | { 29 | public class DbContextMock where TDbContext : DbContext 30 | { 31 | private readonly IKeyFactoryBuilder _keyFactoryBuilder; 32 | private readonly Dictionary _dbSetCache = new Dictionary(); 33 | 34 | public TDbContext Object { get; set; } 35 | 36 | public DbContextMock(params object[] args) 37 | : this(new AttributeBasedKeyFactoryBuilder(), args) 38 | { 39 | } 40 | 41 | private DbContextMock(IKeyFactoryBuilder keyFactoryBuilder, params object[] args) 42 | { 43 | Object = Substitute.For(args); 44 | _keyFactoryBuilder = keyFactoryBuilder ?? throw new ArgumentNullException(nameof(keyFactoryBuilder)); 45 | Reset(); 46 | } 47 | 48 | public DbSetMock CreateDbSetMock(Expression>> dbSetSelector, IEnumerable initialEntities = null) 49 | where TEntity : class 50 | => CreateDbSetMock(dbSetSelector, _keyFactoryBuilder.BuildKeyFactory(), initialEntities); 51 | 52 | public DbSetMock CreateDbSetMock( 53 | Expression>> dbSetSelector, 54 | Func entityKeyFactory, 55 | IEnumerable initialEntities = null) 56 | where TEntity : class 57 | { 58 | if (dbSetSelector == null) throw new ArgumentNullException(nameof(dbSetSelector)); 59 | if (entityKeyFactory == null) throw new ArgumentNullException(nameof(entityKeyFactory)); 60 | 61 | var memberInfo = ((MemberExpression)dbSetSelector.Body).Member; 62 | if (_dbSetCache.ContainsKey(memberInfo)) throw new ArgumentException($"DbSetMock for {memberInfo.Name} already created", nameof(dbSetSelector)); 63 | var mock = new DbSetMock(initialEntities, entityKeyFactory); 64 | Object.Set().Returns(mock.Object); 65 | 66 | dbSetSelector.Compile()(Object).Returns(mock.Object); 67 | 68 | _dbSetCache.Add(memberInfo, mock); 69 | return mock; 70 | } 71 | 72 | public void Reset() 73 | { 74 | _dbSetCache.Clear(); 75 | Object.ClearReceivedCalls(); 76 | Object.SaveChanges().Returns(a => SaveChanges()); 77 | Object.SaveChangesAsync().Returns(a => SaveChanges()); 78 | Object.SaveChangesAsync(Arg.Any()).Returns(a => SaveChanges()); 79 | } 80 | 81 | // Facilitates unit-testing 82 | internal void RegisterDbSetMock(Expression>> dbSetSelector, IDbSetMock dbSet) 83 | where TEntity : class 84 | { 85 | var memberInfo = ((MemberExpression)dbSetSelector.Body).Member; 86 | _dbSetCache.Add(memberInfo, dbSet); 87 | } 88 | 89 | private int SaveChanges() => _dbSetCache.Values.Aggregate(0, (seed, dbSet) => seed + dbSet.SaveChanges()); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/EntityFrameworkMock.NSubstitute/DbSetMock.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2019 Paul Michaels, Wouter Huysentruit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | using System; 18 | using System.Collections; 19 | using System.Collections.Generic; 20 | using System.Data.Entity; 21 | using System.Data.Entity.Infrastructure; 22 | using System.Linq; 23 | using System.Threading; 24 | using System.Threading.Tasks; 25 | using NSubstitute; 26 | 27 | namespace EntityFrameworkMock.NSubstitute 28 | { 29 | public class DbSetMock : IDbSetMock 30 | where TEntity : class 31 | { 32 | private readonly DbSetBackingStore _store; 33 | 34 | public DbSet Object { get; } 35 | 36 | public DbSetMock(IEnumerable initialEntities, Func keyFactory, bool asyncQuerySupport = true) 37 | { 38 | _store = new DbSetBackingStore(initialEntities, keyFactory); 39 | 40 | var data = _store.GetDataAsQueryable(); 41 | Object = Substitute.For, IQueryable, IDbAsyncEnumerable>(); 42 | 43 | ((IQueryable)Object).Provider.Returns(asyncQuerySupport ? new DbAsyncQueryProvider(data.Provider) : data.Provider); 44 | Object.AsQueryable().Provider.Returns(asyncQuerySupport ? new DbAsyncQueryProvider(data.Provider) : data.Provider); 45 | Object.AsQueryable().Expression.Returns(data.Expression); 46 | Object.AsQueryable().ElementType.Returns(data.ElementType); 47 | ((IQueryable)Object).GetEnumerator().Returns(_ => data.GetEnumerator()); 48 | ((IEnumerable)Object).GetEnumerator().Returns(_ => data.GetEnumerator()); 49 | 50 | if (asyncQuerySupport) 51 | { 52 | ((IDbAsyncEnumerable)Object).GetAsyncEnumerator().Returns(_ => new DbAsyncEnumerator(data.GetEnumerator())); 53 | } 54 | 55 | Object.AsNoTracking().Returns(Object); 56 | Object.Include(Arg.Any()).Returns(Object); 57 | Object.AsNoTracking().Include(Arg.Any()).Returns(Object); 58 | 59 | Object.When(a => a.Add(Arg.Any())).Do(x => _store.Add(x.ArgAt(0))); 60 | Object.When(a => a.AddRange(Arg.Any>())).Do(x => _store.Add(x.ArgAt>(0))); 61 | Object.When(a => a.Remove(Arg.Any())).Do(x => _store.Remove(x.ArgAt(0))); 62 | Object.When(a => a.RemoveRange(Arg.Any>())).Do(x => _store.Remove(x.ArgAt>(0))); 63 | 64 | Object.Find(Arg.Any()).Returns(info => _store.Find(info.Args()[0] as object[])); 65 | Object.FindAsync(Arg.Any(), Arg.Any()).Returns(info => Task.FromResult(_store.Find(info.Args()[1] as object[]))); 66 | 67 | _store.UpdateSnapshot(); 68 | } 69 | 70 | public event EventHandler> SavedChanges; 71 | 72 | int IDbSetMock.SaveChanges() 73 | { 74 | var changes = _store.ApplyChanges(); 75 | SavedChanges?.Invoke(this, new SavedChangesEventArgs { UpdatedEntities = _store.GetUpdatedEntities() }); 76 | _store.UpdateSnapshot(); 77 | return changes; 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/EntityFrameworkMock.NSubstitute/EntityFrameworkMock.NSubstitute.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net472;netstandard2.1 5 | EntityFrameworkMock.NSubstitute 6 | EntityFrameworkMock 7 | 1.0.0.0 8 | Wouter Huysentruit, Paul Michaels 9 | EntityFrameworkMock 10 | https://raw.githubusercontent.com/huysentruitw/entity-framework-mock/master/LICENSE 11 | https://github.com/huysentruitw/entity-framework-mock 12 | false 13 | Easy Mock wrapper for mocking EF6 DbContext and DbSet using NSubstitute 14 | Copyright 2017-2019 Wouter Huysentruit, Paul Michaels 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/EntityFrameworkMock.NSubstitute/_Visibility.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly:InternalsVisibleTo("EntityFrameworkMock.NSubstitute.Tests")] -------------------------------------------------------------------------------- /src/EntityFrameworkMock.Shared/AttributeBasedKeyFactoryBuilder.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2019 Wouter Huysentruit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | using System; 18 | using System.ComponentModel.DataAnnotations.Schema; 19 | using System.Linq; 20 | using System.Linq.Expressions; 21 | using System.Reflection; 22 | 23 | namespace EntityFrameworkMock 24 | { 25 | public sealed class AttributeBasedKeyFactoryBuilder : IKeyFactoryBuilder 26 | where TAttribute : Attribute 27 | { 28 | public Func BuildKeyFactory() 29 | { 30 | var entityType = typeof(T); 31 | var keyProperties = entityType.GetProperties(BindingFlags.Instance | BindingFlags.Public) 32 | .Where(x => x.GetCustomAttribute(typeof(TAttribute)) != null) 33 | .ToArray(); 34 | 35 | if (!keyProperties.Any()) throw new InvalidOperationException($"Entity type {entityType.Name} does not contain any property marked with {typeof(TAttribute).Name}"); 36 | 37 | var keyFactory = BuildIdentityKeyFactory(keyProperties); 38 | keyFactory = keyFactory ?? BuildDefaultKeyFactory(keyProperties); 39 | return keyFactory; 40 | } 41 | 42 | private static Func BuildIdentityKeyFactory(PropertyInfo[] keyProperties) 43 | { 44 | if (keyProperties.Length != 1) return null; 45 | var keyProperty = keyProperties[0]; 46 | if (keyProperty == null) return null; 47 | var databaseGeneratedAttribute = keyProperty.GetCustomAttribute(typeof(DatabaseGeneratedAttribute)) as DatabaseGeneratedAttribute; 48 | if (databaseGeneratedAttribute?.DatabaseGeneratedOption != DatabaseGeneratedOption.Identity) return null; 49 | 50 | var entityArgument = Expression.Parameter(typeof(T)); 51 | var keyContextArgument = Expression.Parameter(typeof(KeyContext)); 52 | 53 | if (keyProperty.PropertyType == typeof(int)) 54 | { 55 | return BuildIdentityKeyFactory(keyProperty, ctx => Expression.Property(ctx, nameof(KeyContext.NextIdentity))); 56 | } 57 | else if (keyProperty.PropertyType == typeof(long)) 58 | { 59 | return BuildIdentityKeyFactory(keyProperty, ctx => Expression.Property(ctx, nameof(KeyContext.NextIdentity))); 60 | } 61 | else if (keyProperty.PropertyType == typeof(Guid)) 62 | { 63 | return BuildIdentityKeyFactory(keyProperty, _ => Expression.Call(typeof(Guid), nameof(Guid.NewGuid), new Type[0])); 64 | } 65 | 66 | return null; 67 | } 68 | 69 | private static Func BuildIdentityKeyFactory( 70 | PropertyInfo keyProperty, 71 | Func nextIdentity) 72 | { 73 | var entityArgument = Expression.Parameter(typeof(TEntity)); 74 | var keyContextArgument = Expression.Parameter(typeof(KeyContext)); 75 | var keyValueVariable = Expression.Variable(typeof(TKey)); 76 | var body = Expression.Block(typeof(object), 77 | new[] { keyValueVariable }, 78 | Expression.Assign(keyValueVariable, Expression.Convert(Expression.Property(entityArgument, keyProperty), typeof(TKey))), 79 | Expression.IfThen(Expression.Equal(keyValueVariable, Expression.Default(typeof(TKey))), 80 | Expression.Block( 81 | Expression.Assign(keyValueVariable, Expression.Convert(nextIdentity(keyContextArgument), typeof(TKey))), 82 | Expression.Assign(Expression.Property(entityArgument, keyProperty), keyValueVariable) 83 | ) 84 | ), 85 | Expression.Convert(keyValueVariable, typeof(object))); 86 | 87 | return Expression.Lambda>(body, entityArgument, keyContextArgument).Compile(); 88 | } 89 | 90 | private static Func BuildDefaultKeyFactory(PropertyInfo[] keyProperties) 91 | { 92 | var entityType = typeof(T); 93 | 94 | var tupleType = Type.GetType($"System.Tuple`{keyProperties.Length}"); 95 | if (tupleType == null) throw new InvalidOperationException($"No tuple type found for {keyProperties.Length} generic arguments"); 96 | 97 | var keyPropertyTypes = keyProperties.Select(x => x.PropertyType).ToArray(); 98 | var constructor = tupleType.MakeGenericType(keyPropertyTypes).GetConstructor(keyPropertyTypes); 99 | if (constructor == null) throw new InvalidOperationException($"No tuple constructor found for key in {entityType.Name} entity"); 100 | 101 | var entityArgument = Expression.Parameter(entityType); 102 | var keyContextArgument = Expression.Parameter(typeof(KeyContext)); 103 | var newTupleExpression = Expression.New(constructor, keyProperties.Select(x => Expression.Property(entityArgument, x))); 104 | return Expression.Lambda>(newTupleExpression, entityArgument, keyContextArgument).Compile(); 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/EntityFrameworkMock.Shared/DbAsyncEnumerable.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Data.Entity.Infrastructure; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | 6 | namespace EntityFrameworkMock 7 | { 8 | // From https://msdn.microsoft.com/en-us/library/dn314429(v=vs.113).aspx 9 | public class DbAsyncEnumerable : EnumerableQuery, IDbAsyncEnumerable, IQueryable 10 | { 11 | public DbAsyncEnumerable(IEnumerable enumerable) 12 | : base(enumerable) 13 | { 14 | } 15 | 16 | public DbAsyncEnumerable(Expression expression) 17 | : base(expression) 18 | { 19 | } 20 | 21 | public IDbAsyncEnumerator GetAsyncEnumerator() => new DbAsyncEnumerator(this.AsEnumerable().GetEnumerator()); 22 | 23 | IDbAsyncEnumerator IDbAsyncEnumerable.GetAsyncEnumerator() => GetAsyncEnumerator(); 24 | 25 | IQueryProvider IQueryable.Provider => new DbAsyncQueryProvider(this); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/EntityFrameworkMock.Shared/DbAsyncEnumerator.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Data.Entity.Infrastructure; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace EntityFrameworkMock 7 | { 8 | // From https://msdn.microsoft.com/en-us/library/dn314429(v=vs.113).aspx 9 | public class DbAsyncEnumerator : IDbAsyncEnumerator 10 | { 11 | private readonly IEnumerator _inner; 12 | 13 | public DbAsyncEnumerator(IEnumerator inner) 14 | { 15 | _inner = inner; 16 | } 17 | 18 | public void Dispose() => _inner.Dispose(); 19 | 20 | public Task MoveNextAsync(CancellationToken cancellationToken) => Task.FromResult(_inner.MoveNext()); 21 | 22 | public TEntity Current => _inner.Current; 23 | 24 | object IDbAsyncEnumerator.Current => Current; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/EntityFrameworkMock.Shared/DbAsyncQueryProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data.Entity.Infrastructure; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | namespace EntityFrameworkMock 9 | { 10 | // From https://msdn.microsoft.com/en-us/library/dn314429(v=vs.113).aspx 11 | public class DbAsyncQueryProvider : IDbAsyncQueryProvider 12 | { 13 | private readonly IQueryProvider _inner; 14 | 15 | public DbAsyncQueryProvider(IQueryProvider inner) 16 | { 17 | _inner = inner; 18 | } 19 | 20 | public IQueryable CreateQuery(Expression expression) 21 | { 22 | if (expression is MethodCallExpression methodCallExpression) 23 | { 24 | var resultType = methodCallExpression.Method.ReturnType; 25 | var genericElement = resultType.GetGenericArguments()[0]; 26 | var queryType = typeof(DbAsyncEnumerable<>).MakeGenericType(genericElement); 27 | return (IQueryable)Activator.CreateInstance(queryType, expression); 28 | } 29 | 30 | return new DbAsyncEnumerable(expression); 31 | } 32 | 33 | public IQueryable CreateQuery(Expression expression) => new DbAsyncEnumerable(expression); 34 | 35 | public object Execute(Expression expression) => _inner.Execute(expression); 36 | 37 | public TResult Execute(Expression expression) => _inner.Execute(expression); 38 | 39 | public Task ExecuteAsync(Expression expression, CancellationToken cancellationToken) => Task.FromResult(Execute(expression)); 40 | 41 | public Task ExecuteAsync(Expression expression, CancellationToken cancellationToken) => Task.FromResult(Execute(expression)); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/EntityFrameworkMock.Shared/DbSetBackingStore.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2019 Wouter Huysentruit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | using System; 18 | using System.Collections.Concurrent; 19 | using System.Collections.Generic; 20 | using System.ComponentModel.DataAnnotations.Schema; 21 | using System.Data.Entity.Core; 22 | using System.Data.Entity.Infrastructure; 23 | using System.Linq; 24 | using System.Linq.Expressions; 25 | using System.Reflection; 26 | using System.Threading; 27 | 28 | namespace EntityFrameworkMock 29 | { 30 | public sealed class DbSetBackingStore 31 | where TEntity : class 32 | { 33 | private readonly KeyFactoryNormalizer _keyFactoryNormalizer; 34 | private readonly Dictionary _entities = new Dictionary(); 35 | private readonly Dictionary _snapshot = new Dictionary(); 36 | private List _changes = new List(); 37 | private readonly KeyContext _keyContext = new KeyContext(); 38 | 39 | public DbSetBackingStore(IEnumerable initialEntities, Func keyFactory) 40 | { 41 | _keyFactoryNormalizer = new KeyFactoryNormalizer(keyFactory ?? throw new ArgumentNullException(nameof(keyFactory))); 42 | initialEntities?.ToList().ForEach(x => _entities.Add(_keyFactoryNormalizer.GenerateKey(x, _keyContext), Clone(x))); 43 | } 44 | 45 | public IQueryable GetDataAsQueryable() => _entities.Values.AsQueryable(); 46 | 47 | /// 48 | /// Registers the addition of a new entity. 49 | /// 50 | /// The new entity. 51 | public void Add(TEntity entity) => _changes.Add(DbSetChange.Add(entity)); 52 | 53 | /// 54 | /// Registers the addition of one or more entities. 55 | /// 56 | /// The list of entities. 57 | public void Add(IEnumerable entities) => _changes.AddRange(DbSetChange.Add(entities)); 58 | 59 | /// 60 | /// Find an entity by its key. 61 | /// 62 | /// The key. 63 | /// The entity or null in case no entity with a matching key was found. 64 | public TEntity Find(object[] keyValues) 65 | { 66 | var tupleType = Type.GetType($"System.Tuple`{keyValues.Length}"); 67 | if (tupleType == null) throw new InvalidOperationException($"No tuple type found for {keyValues.Length} generic arguments"); 68 | 69 | var keyTypes = keyValues.Select(x => x.GetType()).ToArray(); 70 | var constructor = tupleType.MakeGenericType(keyTypes).GetConstructor(keyTypes); 71 | if (constructor == null) throw new InvalidOperationException("No tuple constructor found for key values"); 72 | 73 | var key = constructor.Invoke(keyValues); 74 | return _entities.TryGetValue(key, out var entity) ? entity : null; 75 | } 76 | 77 | /// 78 | /// Registers the removal of an entity. 79 | /// 80 | /// The removed entity. 81 | public void Remove(TEntity entity) => _changes.Add(DbSetChange.Remove(entity)); 82 | 83 | /// 84 | /// Registers the removal of one or more entities. 85 | /// 86 | /// The list of removed entities. 87 | public void Remove(IEnumerable entities) => _changes.AddRange(DbSetChange.Remove(entities)); 88 | 89 | /// 90 | /// Applies the registered changes to the collection of entities. 91 | /// 92 | /// The number of changes that got applied. 93 | public int ApplyChanges() 94 | { 95 | var changes = Interlocked.Exchange(ref _changes, new List()); 96 | foreach (var change in changes) 97 | { 98 | if (change.IsAdd) AddEntity(change.Entity); 99 | else if (change.IsRemove) RemoveEntity(change.Entity); 100 | } 101 | 102 | return changes.Count; 103 | } 104 | 105 | /// 106 | /// Updates the snapshot of the entities that is used to detect updated properties. 107 | /// 108 | public void UpdateSnapshot() 109 | { 110 | _snapshot.Clear(); 111 | foreach (var kvp in _entities) 112 | _snapshot.Add(kvp.Key, Clone(kvp.Value)); 113 | } 114 | 115 | /// 116 | /// Gets a list of entities that have one or more properties updated (as compared to the last snapshot). 117 | /// 118 | /// The list of updated entities. 119 | public UpdatedEntityInfo[] GetUpdatedEntities() 120 | { 121 | return _entities 122 | .Join( 123 | _snapshot, 124 | entity => entity.Key, 125 | snapshot => snapshot.Key, 126 | (entity, snapshot) => 127 | new UpdatedEntityInfo 128 | { 129 | Entity = entity.Value, 130 | UpdatedProperties = Diff(snapshot.Value, entity.Value) 131 | } 132 | ) 133 | .Where(x => x.UpdatedProperties.Any()) 134 | .ToArray(); 135 | } 136 | 137 | private void AddEntity(TEntity entity) 138 | { 139 | var key = _keyFactoryNormalizer.GenerateKey(entity, _keyContext); 140 | if (_entities.ContainsKey(key)) ThrowDbUpdateException(); 141 | _entities.Add(key, entity); 142 | } 143 | 144 | private void RemoveEntity(TEntity entity) 145 | { 146 | var key = _keyFactoryNormalizer.GenerateKey(entity, _keyContext); 147 | if (!_entities.Remove(key)) ThrowDbUpdateConcurrencyException(); 148 | } 149 | 150 | private static void ThrowDbUpdateException() 151 | { 152 | const string message = "Violation of PRIMARY KEY constraint 'KEY'. Cannot insert duplicate key in object 'SCHEMA.TABLE'. The duplicate key value is ().\nThe statement has been terminated."; 153 | const string seeInnerExceptionMessage = "An error occurred while updating the entries. See the inner exception for details."; 154 | var sqlException = SqlExceptionCreator.Create(message, 2627); 155 | var updateException = new UpdateException(seeInnerExceptionMessage, sqlException); 156 | throw new DbUpdateException(seeInnerExceptionMessage, updateException); 157 | } 158 | 159 | private static void ThrowDbUpdateConcurrencyException() 160 | { 161 | const string message = "Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=472540 for information on understanding and handling optimistic concurrency exceptions."; 162 | var innerException = new OptimisticConcurrencyException(message); 163 | throw new DbUpdateConcurrencyException(innerException.Message, innerException); 164 | } 165 | 166 | private static UpdatePropertyInfo[] Diff(TEntity snapshot, TEntity current) 167 | { 168 | var properties = snapshot.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public) 169 | .Where(x => x.CanRead && x.CanWrite && x.GetCustomAttribute() == null) 170 | .ToArray(); 171 | 172 | return properties 173 | .Select(x => new UpdatePropertyInfo 174 | { 175 | Name = x.Name, 176 | Original = x.GetValue(snapshot), 177 | New = x.GetValue(current) 178 | }) 179 | .Where(x => !object.Equals(x.New, x.Original)) 180 | .ToArray(); 181 | } 182 | 183 | private static TEntity Clone(TEntity original) => CloneFuncCache.GetOrAdd(original.GetType(), CreateCloneFunc)(original); 184 | private static readonly ConcurrentDictionary> CloneFuncCache = new ConcurrentDictionary>(); 185 | private static Func CreateCloneFunc(Type entityType) 186 | { 187 | var properties = entityType.GetProperties(BindingFlags.Instance | BindingFlags.Public) 188 | .Where(x => x.CanRead && x.CanWrite && x.GetCustomAttribute() == null) 189 | .ToArray(); 190 | 191 | var original = Expression.Parameter(typeof(TEntity), "original"); 192 | var clone = Expression.Variable(entityType, "clone"); 193 | var newClone = Expression.New(entityType); 194 | var cloneBlock = Expression.Block( 195 | new[] { clone }, 196 | Expression.Assign(clone, newClone), 197 | Expression.Block( 198 | properties.Select(propertyInfo => 199 | { 200 | var getter = Expression.Property(Expression.Convert(original, entityType), propertyInfo); 201 | var setter = propertyInfo.GetSetMethod(); 202 | return Expression.Call(clone, setter, getter); 203 | }) 204 | ), 205 | clone); 206 | 207 | return Expression.Lambda>(cloneBlock, original).Compile(); 208 | } 209 | 210 | private class DbSetChange 211 | { 212 | public bool IsAdd { get; private set; } 213 | 214 | public bool IsRemove { get; private set; } 215 | 216 | public TEntity Entity { get; private set; } 217 | 218 | public static DbSetChange Add(TEntity entity) => new DbSetChange { IsAdd = true, Entity = entity }; 219 | 220 | public static IEnumerable Add(IEnumerable entities) => entities.Select(DbSetChange.Add); 221 | 222 | public static DbSetChange Remove(TEntity entity) => new DbSetChange { IsRemove = true, Entity = entity }; 223 | 224 | public static IEnumerable Remove(IEnumerable entities) => entities.Select(DbSetChange.Remove); 225 | } 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /src/EntityFrameworkMock.Shared/EntityFrameworkMock.Shared.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net472;netstandard2.1 5 | EntityFrameworkMock.Shared 6 | EntityFrameworkMock 7 | 1.0.0.0 8 | Wouter Huysentruit 9 | Wouter Huysentruit 10 | https://raw.githubusercontent.com/huysentruitw/entity-framework-mock/master/LICENSE 11 | https://github.com/huysentruitw/entity-framework-mock 12 | false 13 | Shared code for EntityFrameworkMock.Moq 14 | Copyright 2017-2019 Wouter Huysentruit 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/EntityFrameworkMock.Shared/IDbSetMock.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2019 Wouter Huysentruit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | namespace EntityFrameworkMock 18 | { 19 | public interface IDbSetMock 20 | { 21 | int SaveChanges(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/EntityFrameworkMock.Shared/IKeyFactoryBuilder.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2019 Wouter Huysentruit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | using System; 18 | 19 | namespace EntityFrameworkMock 20 | { 21 | public interface IKeyFactoryBuilder 22 | { 23 | Func BuildKeyFactory(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/EntityFrameworkMock.Shared/KeyContext.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2019 Wouter Huysentruit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | namespace EntityFrameworkMock 18 | { 19 | public sealed class KeyContext 20 | { 21 | private long _nextIdentity = 1; 22 | 23 | public long NextIdentity => _nextIdentity++; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/EntityFrameworkMock.Shared/KeyFactoryNormalizer.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2019 Wouter Huysentruit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | using System; 18 | using System.Linq; 19 | using System.Reflection; 20 | 21 | namespace EntityFrameworkMock 22 | { 23 | internal sealed class KeyFactoryNormalizer 24 | where TEntity : class 25 | { 26 | private readonly Func _keyFactory; 27 | 28 | public KeyFactoryNormalizer(Func keyFactory) 29 | { 30 | _keyFactory = keyFactory; 31 | } 32 | 33 | public object GenerateKey(TEntity entity, KeyContext keyContext) 34 | => NormalizeKey(_keyFactory(entity, keyContext)); 35 | 36 | private static object NormalizeKey(object key) 37 | { 38 | var keyType = key?.GetType(); 39 | if (keyType == null) return null; 40 | 41 | if (keyType.FullName?.StartsWith("System.ValueTuple`") ?? false) 42 | { 43 | var valueTupleTypes = keyType.GetGenericArguments(); 44 | var toTupleMethod = typeof(TupleExtensions).GetMethods(BindingFlags.Public | BindingFlags.Static) 45 | .FirstOrDefault(x => x.Name.Equals(nameof(TupleExtensions.ToTuple)) && x.ReturnType.Name.Equals($"Tuple`{valueTupleTypes.Length}")); 46 | if (toTupleMethod == null) throw new InvalidOperationException($"No {nameof(TupleExtensions.ToTuple)} extension method found"); 47 | toTupleMethod = toTupleMethod?.MakeGenericMethod(valueTupleTypes); 48 | return toTupleMethod.Invoke(null, new[] {key}); 49 | } 50 | 51 | if (!keyType.FullName?.StartsWith("System.Tuple`") ?? false) 52 | { 53 | var tupleType = Type.GetType("System.Tuple`1"); 54 | if (tupleType == null) throw new InvalidOperationException($"No tuple type found for one generic arguments"); 55 | var constructor = tupleType.MakeGenericType(keyType).GetConstructor(new[] {keyType}); 56 | if (constructor == null) throw new InvalidOperationException($"No tuple constructor found for key in entity"); 57 | return constructor.Invoke(new[] {key}); 58 | } 59 | 60 | return key; 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/EntityFrameworkMock.Shared/SavedChangesEventArgs.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2019 Wouter Huysentruit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | using System; 18 | 19 | namespace EntityFrameworkMock 20 | { 21 | public sealed class SavedChangesEventArgs : EventArgs 22 | where TEntity : class 23 | { 24 | public UpdatedEntityInfo[] UpdatedEntities { get; set; } 25 | } 26 | 27 | public sealed class UpdatedEntityInfo 28 | where TEntity : class 29 | { 30 | public TEntity Entity { get; set; } 31 | 32 | public UpdatePropertyInfo[] UpdatedProperties { get; set; } 33 | } 34 | 35 | public sealed class UpdatePropertyInfo 36 | { 37 | public string Name { get; set; } 38 | 39 | public object Original { get; set; } 40 | 41 | public object New { get; set; } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/EntityFrameworkMock.Shared/SqlExceptionCreator.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2019 Wouter Huysentruit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | using System; 18 | using System.Collections; 19 | using System.Collections.Generic; 20 | using System.Data.SqlClient; 21 | using System.Reflection; 22 | using System.Runtime.Serialization; 23 | 24 | namespace EntityFrameworkMock 25 | { 26 | public static class SqlExceptionCreator 27 | { 28 | public static SqlException Create(string message, int errorCode) 29 | { 30 | SqlException exception = Instantiate(); 31 | SetProperty(exception, "_message", message); 32 | 33 | var error = GetSqlError(errorCode); 34 | var errorCollection = GetSqlErrorCollection(error); 35 | 36 | SetProperty(exception, "_errors", errorCollection); 37 | return exception; 38 | } 39 | 40 | private static T Instantiate() where T : class 41 | { 42 | return FormatterServices.GetUninitializedObject(typeof(T)) as T; 43 | } 44 | 45 | private static void SetProperty(T targetObject, string fieldName, object value) 46 | { 47 | var field = typeof(T).GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance); 48 | if (field == null) 49 | { 50 | throw new InvalidOperationException("No field with name " + fieldName); 51 | } 52 | 53 | field.SetValue(targetObject, value); 54 | } 55 | 56 | private static SqlErrorCollection GetSqlErrorCollection(params object[] errors) 57 | { 58 | var field = GetField("errors") 59 | ?? GetField("_errors") 60 | ?? throw new InvalidOperationException("No errors field found"); 61 | 62 | var errorCollection = Instantiate(); 63 | if (field == null) 64 | { 65 | return errorCollection; 66 | } 67 | 68 | var errorsList = GetErrorsList(field.FieldType, errors); 69 | 70 | field.SetValue(errorCollection, errorsList); 71 | 72 | return errorCollection; 73 | } 74 | 75 | private static SqlError GetSqlError(int errorCode) 76 | { 77 | var field = GetField("number") 78 | ?? GetField("_number") 79 | ?? throw new InvalidOperationException("No number field found"); 80 | 81 | var error = Instantiate(); 82 | field.SetValue(error, errorCode); 83 | 84 | return error; 85 | } 86 | 87 | private static FieldInfo GetField(string fieldName) 88 | { 89 | return typeof(T).GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance); 90 | } 91 | 92 | private static object GetErrorsList(Type type, object[] errors) 93 | { 94 | if (type == typeof(ArrayList)) 95 | { 96 | return new ArrayList(errors); 97 | } 98 | 99 | if (type == typeof(List)) 100 | { 101 | return new List(errors); 102 | } 103 | 104 | throw new InvalidOperationException("Error collections of type {type} are not supported"); 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /tests/EntityFrameworkMock.Moq.Tests/AutoMapperRelatedTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data.Entity; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using AutoMapper; 6 | using AutoMapper.QueryableExtensions; 7 | using EntityFrameworkMock.Tests.Models; 8 | using NUnit.Framework; 9 | 10 | namespace EntityFrameworkMock.Moq.Tests 11 | { 12 | [TestFixture] 13 | public class AutoMapperRelatedTests 14 | { 15 | public class UserModel 16 | { 17 | public string FullName { get; set; } 18 | 19 | public string Name { get; set; } 20 | } 21 | 22 | [Test] 23 | public async Task DbSetMock_AutoMapperProjectTo() 24 | { 25 | Mapper.Initialize(cfg => { 26 | cfg.CreateMap() 27 | .ForMember(d => d.Name, opt => opt.MapFrom(s => s.FullName)); 28 | }); 29 | 30 | var dbSetMock = new DbSetMock(new[] 31 | { 32 | new User { Id = Guid.NewGuid(), FullName = "Fake Drake" }, 33 | new User { Id = Guid.NewGuid(), FullName = "Jackira Spicy" } 34 | }, (x, _) => x.Id); 35 | var dbSet = dbSetMock.Object; 36 | 37 | Assert.That(await dbSet.CountAsync(), Is.EqualTo(2)); 38 | 39 | var models = await dbSet 40 | .Where(u => u.FullName != null) 41 | .ProjectTo() 42 | .ToListAsync(); 43 | 44 | Assert.That(models.Count, Is.EqualTo(2)); 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /tests/EntityFrameworkMock.Moq.Tests/DbContextMockTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data.Entity; 3 | using System.Data.Entity.Infrastructure; 4 | using System.Linq; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using EntityFrameworkMock.Moq.Tests.Models; 8 | using EntityFrameworkMock.Tests.Models; 9 | using NUnit.Framework; 10 | 11 | namespace EntityFrameworkMock.Tests 12 | { 13 | [TestFixture] 14 | public class DbContextMockTests 15 | { 16 | [Test] 17 | public void DbContextMock_Constructor_PassConnectionString_ShouldPassConnectionStringToMockedClass() 18 | { 19 | var connectionString = Guid.NewGuid().ToString("N"); 20 | var dbContextMock = new DbContextMock(connectionString); 21 | Assert.That(dbContextMock.Object.ConnectionString, Is.EqualTo(connectionString)); 22 | } 23 | 24 | [Test] 25 | public async Task DbContextMock_Constructor_ShouldSetupSaveChanges() 26 | { 27 | var dbContextMock = new DbContextMock("abc"); 28 | dbContextMock.RegisterDbSetMock(x => x.Users, new TestDbSetMock()); 29 | Assert.That(dbContextMock.Object.SaveChanges(), Is.EqualTo(55861)); 30 | Assert.That(await dbContextMock.Object.SaveChangesAsync(), Is.EqualTo(55861)); 31 | Assert.That(await dbContextMock.Object.SaveChangesAsync(CancellationToken.None), Is.EqualTo(55861)); 32 | } 33 | 34 | [Test] 35 | public void DbContextMock_Reset_ShouldForgetMockedDbSets() 36 | { 37 | var dbContextMock = new DbContextMock("abc"); 38 | dbContextMock.RegisterDbSetMock(x => x.Users, new TestDbSetMock()); 39 | Assert.That(dbContextMock.Object.SaveChanges(), Is.EqualTo(55861)); 40 | dbContextMock.Reset(); 41 | Assert.That(dbContextMock.Object.SaveChanges(), Is.EqualTo(0)); 42 | } 43 | 44 | [Test] 45 | public void DbContextMock_Reset_ShouldResetupSaveChanges() 46 | { 47 | var dbContextMock = new DbContextMock("abc"); 48 | dbContextMock.RegisterDbSetMock(x => x.Users, new TestDbSetMock()); 49 | Assert.That(dbContextMock.Object.SaveChanges(), Is.EqualTo(55861)); 50 | dbContextMock.Reset(); 51 | Assert.That(dbContextMock.Object.SaveChanges(), Is.EqualTo(0)); 52 | dbContextMock.RegisterDbSetMock(x => x.Users, new TestDbSetMock()); 53 | Assert.That(dbContextMock.Object.SaveChanges(), Is.EqualTo(55861)); 54 | } 55 | 56 | [Test] 57 | public void DbContextMock_CreateDbSetMock_CreateIdenticalDbSetMockTwice_ShouldThrowExceptionSecondTime() 58 | { 59 | var dbContextMock = new DbContextMock("abc"); 60 | dbContextMock.CreateDbSetMock(x => x.Users); 61 | var ex = Assert.Throws(() => dbContextMock.CreateDbSetMock(x => x.Users)); 62 | Assert.That(ex.ParamName, Is.EqualTo("dbSetSelector")); 63 | Assert.That(ex.Message, Does.StartWith("DbSetMock for Users already created")); 64 | } 65 | 66 | [Test] 67 | public void DbContextMock_CreateDbSetMock_ShouldSetupMockForDbSetSelector() 68 | { 69 | var dbContextMock = new DbContextMock("abc"); 70 | Assert.That(dbContextMock.Object.Users, Is.Null); 71 | dbContextMock.CreateDbSetMock(x => x.Users); 72 | Assert.That(dbContextMock.Object.Users, Is.Not.Null); 73 | } 74 | 75 | [Test] 76 | public async Task DbContextMock_CreateDbSetMock_PassInitialEntities_DbSetShouldContainInitialEntities() 77 | { 78 | var dbContextMock = new DbContextMock("abc"); 79 | dbContextMock.CreateDbSetMock(x => x.Users, new[] 80 | { 81 | new User { Id = Guid.NewGuid(), FullName = "Eric Cartoon" }, 82 | new User { Id = Guid.NewGuid(), FullName = "Billy Jewel" }, 83 | }); 84 | 85 | Assert.That(dbContextMock.Object.Users.Count(), Is.EqualTo(2)); 86 | Assert.That(await dbContextMock.Object.Users.CountAsync(), Is.EqualTo(2)); 87 | 88 | var result = await dbContextMock.Object.Users.FirstAsync(x => x.FullName.StartsWith("Eric")); 89 | Assert.That(result.FullName, Is.EqualTo("Eric Cartoon")); 90 | 91 | result = dbContextMock.Object.Users.First(x => x.FullName.Contains("Jewel")); 92 | Assert.That(result.FullName, Is.EqualTo("Billy Jewel")); 93 | } 94 | 95 | [Test] 96 | public void DbContextMock_CreateDbSetMock_NoKeyFactoryForModelWithoutKeyAttributes_ShouldThrowException() 97 | { 98 | var dbContextMock = new DbContextMock("abc"); 99 | var ex = Assert.Throws(() => dbContextMock.CreateDbSetMock(x => x.NoKeyModels)); 100 | Assert.That(ex.Message, Is.EqualTo("Entity type NoKeyModel does not contain any property marked with KeyAttribute")); 101 | } 102 | 103 | [Test] 104 | public void DbContextMock_CreateDbSetMock_CustomKeyFactoryForModelWithoutKeyAttributes_ShouldNotThrowException() 105 | { 106 | var dbContextMock = new DbContextMock("abc"); 107 | Assert.DoesNotThrow(() => dbContextMock.CreateDbSetMock(x => x.NoKeyModels, (x, _) => x.Id)); 108 | } 109 | 110 | [Test] 111 | public void DbContextMock_CreateDbSetMock_AddModelWithSameKeyTwice_ShouldThrowDbUpdatedException() 112 | { 113 | var userId = Guid.NewGuid(); 114 | var dbContextMock = new DbContextMock("abc"); 115 | var dbSetMock = dbContextMock.CreateDbSetMock(x => x.Users); 116 | dbSetMock.Object.Add(new User {Id = userId, FullName = "SomeName"}); 117 | dbSetMock.Object.Add(new User {Id = Guid.NewGuid(), FullName = "SomeName"}); 118 | dbContextMock.Object.SaveChanges(); 119 | dbSetMock.Object.Add(new User {Id = userId, FullName = "SomeName"}); 120 | Assert.Throws(() => dbContextMock.Object.SaveChanges()); 121 | } 122 | 123 | [Test] 124 | public void DbContextMock_CreateDbSetMock_DeleteUnknownModel_ShouldThrowDbUpdateConcurrencyException() 125 | { 126 | var dbContextMock = new DbContextMock("abc"); 127 | var dbSetMock = dbContextMock.CreateDbSetMock(x => x.Users); 128 | dbSetMock.Object.Remove(new User {Id = Guid.NewGuid()}); 129 | Assert.Throws(() => dbContextMock.Object.SaveChanges()); 130 | } 131 | 132 | [Test] 133 | public void DbContextMock_CreateDbSetMock_AddMultipleModelsWithDatabaseGeneratedIdentityKey_ShouldGenerateSequentialKey() 134 | { 135 | var dbContextMock = new DbContextMock("abc"); 136 | var dbSetMock = dbContextMock.CreateDbSetMock(x => x.GeneratedKeyModels, new[] 137 | { 138 | new GeneratedKeyModel {Value = "first"}, 139 | new GeneratedKeyModel {Value = "second"} 140 | }); 141 | dbSetMock.Object.Add(new GeneratedKeyModel {Value = "third" }); 142 | dbContextMock.Object.SaveChanges(); 143 | 144 | Assert.That(dbSetMock.Object.Min(x => x.Id), Is.EqualTo(1)); 145 | Assert.That(dbSetMock.Object.Max(x => x.Id), Is.EqualTo(3)); 146 | Assert.That(dbSetMock.Object.First(x => x.Id == 1).Value, Is.EqualTo("first")); 147 | Assert.That(dbSetMock.Object.First(x => x.Id == 2).Value, Is.EqualTo("second")); 148 | Assert.That(dbSetMock.Object.First(x => x.Id == 3).Value, Is.EqualTo("third")); 149 | } 150 | 151 | [Test] 152 | public void DbContextMock_CreateDbSetMock_AddMultipleModelsWithGuidAsDatabaseGeneratedIdentityKey_ShouldGenerateRandomGuidAsKey() 153 | { 154 | var knownId = Guid.NewGuid(); 155 | var dbContextMock = new DbContextMock("abc"); 156 | var dbSetMock = dbContextMock.CreateDbSetMock(x => x.GeneratedGuidKeyModels, new[] 157 | { 158 | new GeneratedGuidKeyModel {Id = knownId, Value = "first"}, 159 | new GeneratedGuidKeyModel {Value = "second"} 160 | }); 161 | dbSetMock.Object.Add(new GeneratedGuidKeyModel { Value = "third" }); 162 | dbContextMock.Object.SaveChanges(); 163 | 164 | var modelWithKnownId = dbSetMock.Object.FirstOrDefault(x => x.Id == knownId); 165 | Assert.That(modelWithKnownId, Is.Not.Null); 166 | Assert.That(modelWithKnownId.Value, Is.EqualTo("first")); 167 | } 168 | 169 | [Test] 170 | public void DbContextMock_CreateDbSetMock_AddMultipleModelsWithLongAsDatabaseGeneratedIdentityKey_ShouldGenerateIncrementalKey() 171 | { 172 | // Arrange 173 | var dbContextMock = new DbContextMock("abc"); 174 | var dbSetMock = dbContextMock.CreateDbSetMock(x => x.IntKeyModels); 175 | 176 | // Act 177 | dbContextMock.Object.IntKeyModels.Add(new IntKeyModel { Url = "A" }); 178 | dbContextMock.Object.IntKeyModels.Add(new IntKeyModel { Url = "B" }); 179 | dbContextMock.Object.IntKeyModels.Add(new IntKeyModel { Url = "C" }); 180 | dbContextMock.Object.SaveChanges(); 181 | 182 | // Assert 183 | Assert.That(dbSetMock.Object.First(x => x.Url == "A").LoggingRepositoryId, Is.EqualTo(1)); 184 | Assert.That(dbSetMock.Object.First(x => x.Url == "B").LoggingRepositoryId, Is.EqualTo(2)); 185 | Assert.That(dbSetMock.Object.First(x => x.Url == "C").LoggingRepositoryId, Is.EqualTo(3)); 186 | } 187 | 188 | [Test] 189 | public void DbContextMock_GenericSet_ShouldReturnDbSetMock() 190 | { 191 | var dbContextMock = new DbContextMock("abc"); 192 | var dbSetMock = dbContextMock.CreateDbSetMock(x => x.Users); 193 | var dbSet = dbContextMock.Object.Set(); 194 | Assert.That(dbSet, Is.Not.Null); 195 | Assert.That(dbSet, Is.EqualTo(dbSetMock.Object)); 196 | } 197 | 198 | public class TestDbSetMock : IDbSetMock 199 | { 200 | public int SaveChanges() => 55861; 201 | } 202 | 203 | public class TestDbContext : DbContext 204 | { 205 | public TestDbContext(string connectionString) 206 | : base(connectionString) 207 | { 208 | ConnectionString = connectionString; 209 | } 210 | 211 | public string ConnectionString { get; } 212 | 213 | public virtual DbSet Users { get; set; } 214 | 215 | public virtual DbSet NoKeyModels { get; set; } 216 | 217 | public virtual DbSet GeneratedKeyModels { get; set; } 218 | 219 | public virtual DbSet GeneratedGuidKeyModels { get; set; } 220 | 221 | public virtual DbSet IntKeyModels { get; set; } 222 | } 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /tests/EntityFrameworkMock.Moq.Tests/DbSetMockTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations.Schema; 4 | using System.Data.Entity; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using AutoMapper; 8 | using AutoMapper.QueryableExtensions; 9 | using EntityFrameworkMock.Tests.Models; 10 | using NUnit.Framework; 11 | 12 | namespace EntityFrameworkMock.Tests 13 | { 14 | [TestFixture] 15 | public class DbSetMockTests 16 | { 17 | [Test] 18 | public void DbSetMock_AsNoTracking_ShouldBeMocked() 19 | { 20 | var dbSetMock = new DbSetMock(null, (x, _) => x.Id); 21 | var dbSet = dbSetMock.Object; 22 | Assert.That(dbSet.AsNoTracking(), Is.EqualTo(dbSet)); 23 | } 24 | 25 | [Test] 26 | public void DbSetMock_Include_ShouldBeMocked() 27 | { 28 | var dbSetMock = new DbSetMock(null, (x, _) => x.Id); 29 | var dbSet = dbSetMock.Object; 30 | Assert.That(dbSet.Include(x => x.User), Is.EqualTo(dbSet)); 31 | } 32 | 33 | [Test] 34 | public void DbSetMock_GivenEntityIsAdded_ShouldAddAfterCallingSaveChanges() 35 | { 36 | var user = new User {Id = Guid.NewGuid(), FullName = "Fake Drake"}; 37 | var dbSetMock = new DbSetMock(null, (x, _) => x.Id); 38 | var dbSet = dbSetMock.Object; 39 | 40 | Assert.That(dbSet.Count(), Is.EqualTo(0)); 41 | dbSet.Add(user); 42 | Assert.That(dbSet.Count(), Is.EqualTo(0)); 43 | ((IDbSetMock)dbSetMock).SaveChanges(); 44 | Assert.That(dbSet.Count(), Is.EqualTo(1)); 45 | Assert.That(dbSet.Any(x => x.Id == user.Id && x.FullName == user.FullName), Is.True); 46 | } 47 | 48 | [Test] 49 | public async Task DbSetMock_AsyncProvider() 50 | { 51 | var user = new User { Id = Guid.NewGuid(), FullName = "Fake Drake" }; 52 | var dbSetMock = new DbSetMock(null, (x, _) => x.Id); 53 | var dbSet = dbSetMock.Object; 54 | 55 | Assert.That(await dbSet.CountAsync(), Is.EqualTo(0)); 56 | dbSet.Add(user); 57 | Assert.That(await dbSet.CountAsync(), Is.EqualTo(0)); 58 | ((IDbSetMock)dbSetMock).SaveChanges(); 59 | Assert.That(await dbSet.CountAsync(), Is.EqualTo(1)); 60 | Assert.That(await dbSet.AnyAsync(x => x.Id == user.Id && x.FullName == user.FullName), Is.True); 61 | } 62 | 63 | [Test] 64 | public void DbSetMock_GivenEntityIsRemoved_ShouldRemoveAfterCallingSaveChanges() 65 | { 66 | var user = new User {Id = Guid.NewGuid(), FullName = "Fake Drake"}; 67 | var dbSetMock = new DbSetMock(new[] 68 | { 69 | user, 70 | new User {Id = Guid.NewGuid(), FullName = "Jackira Spicy"} 71 | }, (x, _) => x.Id); 72 | var dbSet = dbSetMock.Object; 73 | 74 | Assert.That(dbSet.Count(), Is.EqualTo(2)); 75 | dbSet.Remove(new User {Id = user.Id}); 76 | Assert.That(dbSet.Count(), Is.EqualTo(2)); 77 | Assert.That(dbSet.Any(x => x.Id == user.Id && x.FullName == user.FullName), Is.True); 78 | ((IDbSetMock)dbSetMock).SaveChanges(); 79 | Assert.That(dbSet.Count(), Is.EqualTo(1)); 80 | Assert.That(dbSet.Any(x => x.Id == user.Id && x.FullName == user.FullName), Is.False); 81 | } 82 | 83 | [Test] 84 | public void DbSetMock_GivenRangeOfEntitiesIsRemoved_ShouldRemoveAfterCallingSaveChanges() 85 | { 86 | var users = new[] 87 | { 88 | new User {Id = Guid.NewGuid(), FullName = "User 1"}, 89 | new User {Id = Guid.NewGuid(), FullName = "User 2"}, 90 | new User {Id = Guid.NewGuid(), FullName = "User 3"} 91 | }; 92 | var dbSetMock = new DbSetMock(users, (x, _) => x.Id); 93 | var dbSet = dbSetMock.Object; 94 | 95 | Assert.That(dbSet.Count(), Is.EqualTo(3)); 96 | dbSet.RemoveRange(users.Skip(1)); 97 | Assert.That(dbSet.Count(), Is.EqualTo(3)); 98 | ((IDbSetMock)dbSetMock).SaveChanges(); 99 | Assert.That(dbSet.Count(), Is.EqualTo(1)); 100 | Assert.That(dbSet.Any(x => x.FullName == "User 1"), Is.True); 101 | Assert.That(dbSet.Any(x => x.FullName == "User 2"), Is.False); 102 | } 103 | 104 | [Test] 105 | public void DbSetMock_SaveChanges_GivenEntityPropertyIsChanged_ShouldFireSavedChangesEventWithCorrectUpdatedInfo() 106 | { 107 | var userId = Guid.NewGuid(); 108 | var dbSetMock = new DbSetMock(new[] 109 | { 110 | new User {Id = userId, FullName = "Mark Kramer"}, 111 | new User {Id = Guid.NewGuid(), FullName = "Freddy Kipcurry"} 112 | }, (x, _) => x.Id); 113 | var dbSet = dbSetMock.Object; 114 | 115 | Assert.That(dbSet.Count(), Is.EqualTo(2)); 116 | var fetchedUser = dbSet.First(x => x.Id == userId); 117 | fetchedUser.FullName = "Kramer Mark"; 118 | 119 | SavedChangesEventArgs eventArgs = null; 120 | dbSetMock.SavedChanges += (sender, args) => eventArgs = args; 121 | 122 | ((IDbSetMock)dbSetMock).SaveChanges(); 123 | 124 | Assert.That(eventArgs, Is.Not.Null); 125 | Assert.That(eventArgs.UpdatedEntities, Has.Length.EqualTo(1)); 126 | var updatedEntity = eventArgs.UpdatedEntities[0]; 127 | Assert.That(updatedEntity.UpdatedProperties, Has.Length.EqualTo(1)); 128 | var updatedProperty = updatedEntity.UpdatedProperties[0]; 129 | Assert.That(updatedProperty.Name, Is.EqualTo("FullName")); 130 | Assert.That(updatedProperty.Original, Is.EqualTo("Mark Kramer")); 131 | Assert.That(updatedProperty.New, Is.EqualTo("Kramer Mark")); 132 | } 133 | 134 | [Test] 135 | public void DbSetMock_SaveChanges_GivenEntityPropertyMarkedAsNotMapped_ShouldNotMarkNotMappedPropertyAsModified() 136 | { 137 | var dbSetMock = new DbSetMock(new[] 138 | { 139 | new NestedModel {Id = Guid.NewGuid(), NestedDocument = new NestedModel.Document()}, 140 | new NestedModel {Id = Guid.NewGuid(), NestedDocument = new NestedModel.Document()}, 141 | new NestedModel {Id = Guid.NewGuid(), NestedDocument = new NestedModel.Document()} 142 | }, (x, _) => x.Id); 143 | 144 | SavedChangesEventArgs eventArgs = null; 145 | dbSetMock.SavedChanges += (sender, args) => eventArgs = args; 146 | 147 | dbSetMock.Object.First().Value = "abc"; 148 | 149 | ((IDbSetMock)dbSetMock).SaveChanges(); 150 | 151 | Assert.That(eventArgs, Is.Not.Null); 152 | Assert.That(eventArgs.UpdatedEntities, Has.Length.EqualTo(1)); 153 | Assert.That(eventArgs.UpdatedEntities.First().UpdatedProperties, Has.Length.EqualTo(1)); 154 | var updatedProperty = eventArgs.UpdatedEntities.First().UpdatedProperties.First(); 155 | Assert.That(updatedProperty.Name, Is.EqualTo("Value")); 156 | Assert.That(updatedProperty.Original, Is.EqualTo(null)); 157 | Assert.That(updatedProperty.New, Is.EqualTo("abc")); 158 | } 159 | 160 | [Test] 161 | public void DbSetMock_Empty_AsEnumerable_ShouldReturnEmptyEnumerable() 162 | { 163 | var dbSetMock = new DbSetMock(new List(), (x, _) => x.Id); 164 | var nestedModels = dbSetMock.Object.AsEnumerable(); 165 | Assert.That(nestedModels, Is.Not.Null); 166 | Assert.That(nestedModels, Is.Empty); 167 | } 168 | 169 | [Test] 170 | public void DbSetMock_AsEnumerable_ShouldReturnEnumerableCollection() 171 | { 172 | var dbSetMock = new DbSetMock(new[] 173 | { 174 | new NestedModel {Id = Guid.NewGuid(), NestedDocument = new NestedModel.Document()}, 175 | new NestedModel {Id = Guid.NewGuid(), NestedDocument = new NestedModel.Document()} 176 | }, (x, _) => x.Id); 177 | var nestedModels = dbSetMock.Object.AsEnumerable(); 178 | Assert.That(nestedModels, Is.Not.Null); 179 | Assert.That(nestedModels.Count(), Is.EqualTo(2)); 180 | } 181 | 182 | [Test] 183 | public async Task DbSetMock_AsyncProvider_ShouldReturnRequestedModel() 184 | { 185 | var userId = Guid.NewGuid(); 186 | var dbSetMock = new DbSetMock(new[] 187 | { 188 | new User {Id = userId, FullName = "Mark Kramer"}, 189 | new User {Id = Guid.NewGuid(), FullName = "Freddy Kipcurry"} 190 | }, (x, _) => x.Id); 191 | 192 | var model = await dbSetMock.Object.Where(x => x.Id == userId).FirstOrDefaultAsync(); 193 | Assert.That(model, Is.Not.Null); 194 | Assert.That(model.FullName, Is.EqualTo("Mark Kramer")); 195 | } 196 | 197 | [Test] 198 | public void DbSetMock_Find_ShouldReturnRequestedModel() 199 | { 200 | Guid user1 = Guid.NewGuid(), user2 = Guid.NewGuid(); 201 | var dbSetMock = new DbSetMock(new[] 202 | { 203 | new User {Id = user1, FullName = "Mark Kramer"}, 204 | new User {Id = user2, FullName = "Freddy Kipcurry"} 205 | }, (x, _) => x.Id); 206 | 207 | var result = dbSetMock.Object.Find(user2); 208 | Assert.That(result, Is.Not.Null); 209 | Assert.That(result.FullName, Is.EqualTo("Freddy Kipcurry")); 210 | } 211 | 212 | [Test] 213 | public void DbSetMock_Find_UnknownId_ShouldReturnNull() 214 | { 215 | var unknownUser = Guid.NewGuid(); 216 | var dbSetMock = new DbSetMock(new[] 217 | { 218 | new User {Id = Guid.NewGuid(), FullName = "Mark Kramer"}, 219 | new User {Id = Guid.NewGuid(), FullName = "Freddy Kipcurry"} 220 | }, (x, _) => x.Id); 221 | 222 | var result = dbSetMock.Object.Find(unknownUser); 223 | Assert.That(result, Is.Null); 224 | } 225 | 226 | [Test] 227 | public void DbSetMock_FindOnCompositeTupleKey_ShouldReturnRequestedModel() 228 | { 229 | Guid modelId1 = Guid.NewGuid(), modelId2 = Guid.NewGuid(); 230 | var dbSetMock = new DbSetMock(new[] 231 | { 232 | new NoKeyModel {Id = modelId1, Value = "Value 1"}, 233 | new NoKeyModel {Id = modelId2, Value = "Value 2"} 234 | }, (x, _) => new Tuple(x.Id, x.Value)); 235 | 236 | var result = dbSetMock.Object.Find(modelId2, "Value 2"); 237 | Assert.That(result, Is.Not.Null); 238 | Assert.That(result.Id, Is.EqualTo(modelId2)); 239 | Assert.That(result.Value, Is.EqualTo("Value 2")); 240 | } 241 | 242 | [Test] 243 | public void DbSetMock_FindOnCompositeValueTupleKey_ShouldReturnRequestedModel() 244 | { 245 | Guid modelId1 = Guid.NewGuid(), modelId2 = Guid.NewGuid(); 246 | var dbSetMock = new DbSetMock(new[] 247 | { 248 | new NoKeyModel {Id = modelId1, Value = "Value 1"}, 249 | new NoKeyModel {Id = modelId2, Value = "Value 2"} 250 | }, (x, _) => (x.Id, x.Value)); 251 | 252 | var result = dbSetMock.Object.Find(modelId2, "Value 2"); 253 | Assert.That(result, Is.Not.Null); 254 | Assert.That(result.Id, Is.EqualTo(modelId2)); 255 | Assert.That(result.Value, Is.EqualTo("Value 2")); 256 | } 257 | 258 | [Test] 259 | public void DbSetMock_FetchDataWithAutoMapperProjectTo_ShouldNotThrowException() 260 | { 261 | var dbSetMock = new DbSetMock(new[] 262 | { 263 | new User {Id = Guid.NewGuid(), FullName = "Rita Gesmorov"}, 264 | new User {Id = Guid.NewGuid(), FullName = "Kelly Verbier"} 265 | }, (x, _) => x.Id); 266 | 267 | var mapperConfig = new MapperConfiguration(mapper => mapper.CreateMap()); 268 | 269 | Assert.DoesNotThrowAsync(async () => await dbSetMock.Object 270 | .Where(x => x.FullName.Contains("Rita")) 271 | .ProjectTo(mapperConfig) 272 | .ToListAsync()); 273 | } 274 | 275 | public class NestedModel 276 | { 277 | public Guid Id { get; set; } 278 | 279 | public string Value { get; set; } 280 | 281 | [NotMapped] 282 | public Document NestedDocument 283 | { 284 | get { return new Document { Name = Guid.NewGuid().ToString("N") }; } 285 | // ReSharper disable once ValueParameterNotUsed 286 | set { } 287 | } 288 | 289 | public class Document 290 | { 291 | public string Name { get; set; } 292 | } 293 | } 294 | 295 | [Test] 296 | public void DbSetMock_SaveChanges_GivenAbstractEntityModel_ShouldNotThrowException() 297 | { 298 | var dbSetMock = new DbSetMock(new[] 299 | { 300 | new ConcreteModel {Id = Guid.NewGuid()} 301 | }, (x, _) => x.Id); 302 | 303 | ((IDbSetMock)dbSetMock).SaveChanges(); 304 | } 305 | 306 | public abstract class AbstractModel 307 | { 308 | public Guid Id { get; set; } 309 | } 310 | 311 | public class ConcreteModel : AbstractModel 312 | { 313 | public string Name { get; set; } = "SomeName"; 314 | } 315 | } 316 | } 317 | -------------------------------------------------------------------------------- /tests/EntityFrameworkMock.Moq.Tests/EntityFrameworkMock.Moq.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net472;net8.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /tests/EntityFrameworkMock.Moq.Tests/Models/GeneratedGuidKeyModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | using System.ComponentModel.DataAnnotations.Schema; 4 | 5 | namespace EntityFrameworkMock.Tests.Models 6 | { 7 | public class GeneratedGuidKeyModel 8 | { 9 | [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] 10 | public Guid Id { get; set; } 11 | 12 | public string Value { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tests/EntityFrameworkMock.Moq.Tests/Models/GeneratedKeyModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using System.ComponentModel.DataAnnotations.Schema; 3 | 4 | namespace EntityFrameworkMock.Tests.Models 5 | { 6 | public class GeneratedKeyModel 7 | { 8 | [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] 9 | public long Id { get; set; } 10 | 11 | public string Value { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tests/EntityFrameworkMock.Moq.Tests/Models/IntKeyModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using System.ComponentModel.DataAnnotations.Schema; 3 | 4 | namespace EntityFrameworkMock.Moq.Tests.Models 5 | { 6 | [Table("LoggingRepository")] 7 | public class IntKeyModel 8 | { 9 | [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] 10 | public int LoggingRepositoryId { get; set; } 11 | 12 | public string Url { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tests/EntityFrameworkMock.Moq.Tests/Models/NoKeyModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace EntityFrameworkMock.Tests.Models 4 | { 5 | public class NoKeyModel 6 | { 7 | public Guid Id { get; set; } 8 | 9 | public string Value { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tests/EntityFrameworkMock.Moq.Tests/Models/Order.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using System.ComponentModel.DataAnnotations.Schema; 3 | 4 | namespace EntityFrameworkMock.Tests.Models 5 | { 6 | public class Order 7 | { 8 | [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] 9 | public int Id { get; set; } 10 | 11 | public virtual User User { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tests/EntityFrameworkMock.Moq.Tests/Models/User.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | using System.ComponentModel.DataAnnotations.Schema; 4 | 5 | namespace EntityFrameworkMock.Tests.Models 6 | { 7 | public class User 8 | { 9 | [Key, Column(Order = 0)] 10 | public Guid Id { get; set; } 11 | 12 | public string FullName { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tests/EntityFrameworkMock.NSubstitute.Tests/AutoMapperRelatedTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data.Entity; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using AutoMapper; 6 | using AutoMapper.QueryableExtensions; 7 | using EntityFrameworkMock.Tests.Models; 8 | using NUnit.Framework; 9 | 10 | namespace EntityFrameworkMock.NSubstitute.Tests 11 | { 12 | [TestFixture] 13 | public class AutoMapperRelatedTests 14 | { 15 | public class UserModel 16 | { 17 | public string FullName { get; set; } 18 | 19 | public string Name { get; set; } 20 | } 21 | 22 | [Test] 23 | public async Task DbSetMock_AutoMapperProjectTo() 24 | { 25 | Mapper.Initialize(cfg => { 26 | cfg.CreateMap() 27 | .ForMember(d => d.Name, opt => opt.MapFrom(s => s.FullName)); 28 | }); 29 | 30 | var dbSetMock = new DbSetMock(new[] 31 | { 32 | new User { Id = Guid.NewGuid(), FullName = "Fake Drake" }, 33 | new User { Id = Guid.NewGuid(), FullName = "Jackira Spicy" } 34 | }, (x, _) => x.Id); 35 | var dbSet = dbSetMock.Object; 36 | 37 | Assert.That(await dbSet.CountAsync(), Is.EqualTo(2)); 38 | 39 | var models = await dbSet 40 | .Where(u => u.FullName != null) 41 | .ProjectTo() 42 | .ToListAsync(); 43 | 44 | Assert.That(models.Count, Is.EqualTo(2)); 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /tests/EntityFrameworkMock.NSubstitute.Tests/DbContextMockTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data.Entity; 4 | using System.Data.Entity.Infrastructure; 5 | using System.Linq; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using EntityFrameworkMock.NSubstitute.Tests.Models; 9 | using EntityFrameworkMock.Tests.Models; 10 | using NUnit.Framework; 11 | 12 | namespace EntityFrameworkMock.NSubstitute.Tests 13 | { 14 | [TestFixture] 15 | public class DbContextMockTests 16 | { 17 | [Test] 18 | public void DbContextMock_Constructor_PassConnectionString_ShouldPassConnectionStringToMockedClass() 19 | { 20 | // Arrange 21 | var connectionString = @"Server=myServerName\myInstanceName;Database=myDataBase;User Id=myUsername; Password = myPassword;"; 22 | 23 | // Act 24 | var dbContextMock = new DbContextMock(connectionString); 25 | 26 | // Assert 27 | Assert.That(dbContextMock.Object.Database.Connection.ConnectionString, Is.EqualTo(connectionString)); 28 | } 29 | 30 | [Test] 31 | public async Task DbContextMock_Constructor_ShouldSetupSaveChanges() 32 | { 33 | // Arrange 34 | var dbContextMock = new DbContextMock("abc"); 35 | 36 | // Act 37 | dbContextMock.RegisterDbSetMock(x => x.Users, new TestDbSetMock()); 38 | 39 | // Assert 40 | Assert.That(dbContextMock.Object.SaveChanges(), Is.EqualTo(55861)); 41 | Assert.That(await dbContextMock.Object.SaveChangesAsync(), Is.EqualTo(55861)); 42 | Assert.That(await dbContextMock.Object.SaveChangesAsync(CancellationToken.None), Is.EqualTo(55861)); 43 | } 44 | 45 | [Test] 46 | public void DbContextMock_Reset_ShouldForgetMockedDbSets() 47 | { 48 | // Arrange 49 | var dbContextMock = new DbContextMock("abc"); 50 | dbContextMock.RegisterDbSetMock(x => x.Users, new TestDbSetMock()); 51 | Assert.That(dbContextMock.Object.SaveChanges(), Is.EqualTo(55861)); 52 | 53 | // Act 54 | dbContextMock.Reset(); 55 | 56 | // Assert 57 | Assert.That(dbContextMock.Object.SaveChanges(), Is.EqualTo(0)); 58 | } 59 | 60 | [Test] 61 | public void DbContextMock_Reset_ShouldResetupSaveChanges() 62 | { 63 | // Arrange 64 | var dbContextMock = new DbContextMock("abc"); 65 | dbContextMock.RegisterDbSetMock(x => x.Users, new TestDbSetMock()); 66 | Assert.That(dbContextMock.Object.SaveChanges(), Is.EqualTo(55861)); 67 | dbContextMock.Reset(); 68 | Assert.That(dbContextMock.Object.SaveChanges(), Is.EqualTo(0)); 69 | 70 | // Act 71 | dbContextMock.RegisterDbSetMock(x => x.Users, new TestDbSetMock()); 72 | 73 | // Assert 74 | Assert.That(dbContextMock.Object.SaveChanges(), Is.EqualTo(55861)); 75 | } 76 | 77 | [Test] 78 | public void DbContextMock_CreateDbSetMock_CreateIdenticalDbSetMockTwice_ShouldThrowExceptionSecondTime() 79 | { 80 | // Arrange 81 | var dbContextMock = new DbContextMock("abc"); 82 | dbContextMock.CreateDbSetMock(x => x.Users); 83 | 84 | // Act 85 | void CreateDbMock() 86 | { 87 | dbContextMock.CreateDbSetMock(x => x.Users); 88 | } 89 | 90 | // Assert 91 | var ex = Assert.Throws(CreateDbMock); 92 | Assert.That(ex.ParamName, Is.EqualTo("dbSetSelector")); 93 | Assert.That(ex.Message, Does.StartWith("DbSetMock for Users already created")); 94 | } 95 | 96 | [Test] 97 | public void DbContextMock_CreateDbSetMock_ShouldSetupMockForDbSetSelector() 98 | { 99 | var dbContextMock = new DbContextMock("abc"); 100 | Assert.That(dbContextMock.Object.Users, Is.Null); 101 | dbContextMock.CreateDbSetMock(x => x.Users); 102 | Assert.That(dbContextMock.Object.Users, Is.Not.Null); 103 | } 104 | 105 | [Test] 106 | public async Task DbContextMock_CreateDbSetMock_PassInitialEntities_DbSetShouldContainInitialEntities() 107 | { 108 | // Arrange 109 | var dbContextMock = new DbContextMock("abc"); 110 | 111 | // Act 112 | dbContextMock.CreateDbSetMock(x => x.Users, new[] 113 | { 114 | new User { Id = Guid.NewGuid(), FullName = "Eric Cartoon" }, 115 | new User { Id = Guid.NewGuid(), FullName = "Billy Jewel" }, 116 | }); 117 | 118 | // Assert 119 | Assert.That(dbContextMock.Object.Users.Count(), Is.EqualTo(2)); 120 | Assert.That(await dbContextMock.Object.Users.CountAsync(), Is.EqualTo(2)); 121 | 122 | var result = await dbContextMock.Object.Users.FirstAsync(x => x.FullName.StartsWith("Eric")); 123 | Assert.That(result.FullName, Is.EqualTo("Eric Cartoon")); 124 | 125 | result = dbContextMock.Object.Users.First(x => x.FullName.Contains("Jewel")); 126 | Assert.That(result.FullName, Is.EqualTo("Billy Jewel")); 127 | } 128 | 129 | [Test] 130 | public void DbContextMock_CreateDbSetMock_NoKeyFactoryForModelWithoutKeyAttributes_ShouldThrowException() 131 | { 132 | var dbContextMock = new DbContextMock("abc"); 133 | var ex = Assert.Throws(() => dbContextMock.CreateDbSetMock(x => x.NoKeyModels)); 134 | Assert.That(ex.Message, Is.EqualTo("Entity type NoKeyModel does not contain any property marked with KeyAttribute")); 135 | } 136 | 137 | [Test] 138 | public void DbContextMock_CreateDbSetMock_CustomKeyFactoryForModelWithoutKeyAttributes_ShouldNotThrowException() 139 | { 140 | var dbContextMock = new DbContextMock("abc"); 141 | Assert.DoesNotThrow(() => 142 | dbContextMock.CreateDbSetMock( 143 | x => x.NoKeyModels, (x, _) => x.Id)); 144 | } 145 | 146 | [Test] 147 | public void DbContextMock_CreateDbSetMock_AddModelWithSameKeyTwice_ShouldThrowDbUpdatedException() 148 | { 149 | // Arrange 150 | var userId = Guid.NewGuid(); 151 | var dbContextMock = new DbContextMock("abc"); 152 | var dbSetMock = dbContextMock.CreateDbSetMock(x => x.Users); 153 | dbSetMock.Object.Add(new User { Id = userId, FullName = "SomeName" }); 154 | dbSetMock.Object.Add(new User { Id = Guid.NewGuid(), FullName = "SomeName" }); 155 | dbContextMock.Object.SaveChanges(); 156 | dbSetMock.Object.Add(new User { Id = userId, FullName = "SomeName" }); 157 | 158 | // Act 159 | void CallSaveChanges() 160 | { 161 | dbContextMock.Object.SaveChanges(); 162 | } 163 | 164 | // Assert 165 | Assert.Throws(CallSaveChanges); 166 | } 167 | 168 | [Test] 169 | public void DbContextMock_CreateDbSetMock_DeleteUnknownModel_ShouldThrowDbUpdateConcurrencyException() 170 | { 171 | var dbContextMock = new DbContextMock("abc"); 172 | var dbSetMock = dbContextMock.CreateDbSetMock(x => x.Users); 173 | dbSetMock.Object.Remove(new User { Id = Guid.NewGuid() }); 174 | Assert.Throws(() => dbContextMock.Object.SaveChanges()); 175 | } 176 | 177 | [Test] 178 | public void DbContextMock_CreateDbSetMock_AddMultipleModelsWithDatabaseGeneratedIdentityKey_ShouldGenerateSequentialKey() 179 | { 180 | var dbContextMock = new DbContextMock("abc"); 181 | var dbSetMock = dbContextMock.CreateDbSetMock(x => x.GeneratedKeyModels, new[] 182 | { 183 | new GeneratedKeyModel {Value = "first"}, 184 | new GeneratedKeyModel {Value = "second"} 185 | }); 186 | dbSetMock.Object.Add(new GeneratedKeyModel { Value = "third" }); 187 | dbContextMock.Object.SaveChanges(); 188 | 189 | Assert.That(dbSetMock.Object.Min(x => x.Id), Is.EqualTo(1)); 190 | Assert.That(dbSetMock.Object.Max(x => x.Id), Is.EqualTo(3)); 191 | Assert.That(dbSetMock.Object.First(x => x.Id == 1).Value, Is.EqualTo("first")); 192 | Assert.That(dbSetMock.Object.First(x => x.Id == 2).Value, Is.EqualTo("second")); 193 | Assert.That(dbSetMock.Object.First(x => x.Id == 3).Value, Is.EqualTo("third")); 194 | } 195 | 196 | [Test] 197 | public void DbContextMock_CreateDbSetMock_AddMultipleModelsWithGuidAsDatabaseGeneratedIdentityKey_ShouldGenerateRandomGuidAsKey() 198 | { 199 | var knownId = Guid.NewGuid(); 200 | var dbContextMock = new DbContextMock("abc"); 201 | var dbSetMock = dbContextMock.CreateDbSetMock(x => x.GeneratedGuidKeyModels, new[] 202 | { 203 | new GeneratedGuidKeyModel {Id = knownId, Value = "first"}, 204 | new GeneratedGuidKeyModel {Value = "second"} 205 | }); 206 | dbSetMock.Object.Add(new GeneratedGuidKeyModel { Value = "third" }); 207 | dbContextMock.Object.SaveChanges(); 208 | 209 | var modelWithKnownId = dbSetMock.Object.FirstOrDefault(x => x.Id == knownId); 210 | Assert.That(modelWithKnownId, Is.Not.Null); 211 | Assert.That(modelWithKnownId.Value, Is.EqualTo("first")); 212 | } 213 | 214 | [Test] 215 | public void DbContextMock_MultipleGets_ShouldReturnDataEachTime() 216 | { 217 | // Arrange 218 | var users = new List() 219 | { 220 | new User() { Id = Guid.NewGuid(), FullName = "Ian Kilmister" }, 221 | new User() { Id = Guid.NewGuid(), FullName = "Phil Taylor" }, 222 | new User() { Id = Guid.NewGuid(), FullName = "Eddie Clarke" } 223 | }; 224 | 225 | var dbContextMock = new DbContextMock("abc"); 226 | dbContextMock.CreateDbSetMock(x => x.Users, users); 227 | 228 | List results = new List(); 229 | 230 | // Act 231 | for (int i = 1; i <= 100; i++) 232 | { 233 | var readUsers = dbContextMock.Object.Users; 234 | foreach (var user in readUsers) 235 | { 236 | results.Add(user.FullName); 237 | } 238 | } 239 | 240 | // Assert 241 | Assert.That(300, Is.EqualTo(results.Count)); 242 | foreach (var result in results) 243 | { 244 | Assert.That(result, Is.Not.Empty); 245 | } 246 | } 247 | 248 | [Test] 249 | public void DbContextMock_CreateDbSetMock_AddMultipleModelsWithLongAsDatabaseGeneratedIdentityKey_ShouldGenerateIncrementalKey() 250 | { 251 | // Arrange 252 | var dbContextMock = new DbContextMock("abc"); 253 | var dbSetMock = dbContextMock.CreateDbSetMock(x => x.IntKeyModels); 254 | 255 | // Act 256 | dbContextMock.Object.IntKeyModels.Add(new IntKeyModel { Url = "A" }); 257 | dbContextMock.Object.IntKeyModels.Add(new IntKeyModel { Url = "B" }); 258 | dbContextMock.Object.IntKeyModels.Add(new IntKeyModel { Url = "C" }); 259 | dbContextMock.Object.SaveChanges(); 260 | 261 | // Assert 262 | Assert.That(dbSetMock.Object.First(x => x.Url == "A").LoggingRepositoryId, Is.EqualTo(1)); 263 | Assert.That(dbSetMock.Object.First(x => x.Url == "B").LoggingRepositoryId, Is.EqualTo(2)); 264 | Assert.That(dbSetMock.Object.First(x => x.Url == "C").LoggingRepositoryId, Is.EqualTo(3)); 265 | } 266 | 267 | [Test] 268 | public void DbContextMock_GenericSet_ShouldReturnDbSetMock() 269 | { 270 | var dbContextMock = new DbContextMock("abc"); 271 | var dbSetMock = dbContextMock.CreateDbSetMock(x => x.Users); 272 | var dbSet = dbContextMock.Object.Set(); 273 | Assert.That(dbSet, Is.Not.Null); 274 | Assert.That(dbSet, Is.EqualTo(dbSetMock.Object)); 275 | } 276 | 277 | public class TestDbSetMock : IDbSetMock 278 | { 279 | public int SaveChanges() => 55861; 280 | } 281 | 282 | public class TestDbContext : DbContext 283 | { 284 | public TestDbContext(string connectionString) 285 | : base(connectionString) 286 | { 287 | ConnectionString = connectionString; 288 | } 289 | 290 | public string ConnectionString { get; } 291 | 292 | public virtual DbSet Users { get; set; } 293 | 294 | public virtual DbSet NoKeyModels { get; set; } 295 | 296 | public virtual DbSet GeneratedKeyModels { get; set; } 297 | 298 | public virtual DbSet GeneratedGuidKeyModels { get; set; } 299 | 300 | public virtual DbSet IntKeyModels { get; set; } 301 | } 302 | } 303 | } 304 | -------------------------------------------------------------------------------- /tests/EntityFrameworkMock.NSubstitute.Tests/DbSetMockTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations.Schema; 4 | using System.Data.Entity; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using AutoMapper; 8 | using AutoMapper.QueryableExtensions; 9 | using EntityFrameworkMock.Tests.Models; 10 | using NUnit.Framework; 11 | 12 | namespace EntityFrameworkMock.NSubstitute.Tests 13 | { 14 | [TestFixture] 15 | public class DbSetMockTests 16 | { 17 | 18 | [Test] 19 | public void DbSetMock_AsNoTracking_ShouldBeMocked() 20 | { 21 | var dbSetMock = new DbSetMock(null, (x, _) => x.Id); 22 | var dbSet = dbSetMock.Object; 23 | Assert.That(dbSet.AsNoTracking(), Is.EqualTo(dbSet)); 24 | } 25 | 26 | [Test] 27 | public void DbSetMock_Include_ShouldBeMocked() 28 | { 29 | var dbSetMock = new DbSetMock(null, (x, _) => x.Id); 30 | var dbSet = dbSetMock.Object; 31 | Assert.That(dbSet.Include(x => x.User), Is.EqualTo(dbSet)); 32 | } 33 | 34 | [Test] 35 | public void DbSetMock_GivenEntityIsAdded_ShouldAddAfterCallingSaveChanges() 36 | { 37 | var user = new User { Id = Guid.NewGuid(), FullName = "Fake Drake" }; 38 | var dbSetMock = new DbSetMock(null, (x, _) => x.Id); 39 | var dbSet = dbSetMock.Object; 40 | 41 | Assert.That(dbSet.Count(), Is.EqualTo(0)); 42 | dbSet.Add(user); 43 | Assert.That(dbSet.Count(), Is.EqualTo(0)); 44 | ((IDbSetMock)dbSetMock).SaveChanges(); 45 | Assert.That(dbSet.Count(), Is.EqualTo(1)); 46 | Assert.That(dbSet.Any(x => x.Id == user.Id && x.FullName == user.FullName), Is.True); 47 | } 48 | 49 | [Test] 50 | public void DbSetMock_GivenEntityRangeIsAdded_ShouldAddAfterCallingSaveChanges() 51 | { 52 | // Arrange 53 | var users = new List() 54 | { 55 | new User() { Id = Guid.NewGuid(), FullName = "Ian Kilmister" }, 56 | new User() { Id = Guid.NewGuid(), FullName = "Phil Taylor" }, 57 | new User() { Id = Guid.NewGuid(), FullName = "Eddie Clarke" } 58 | }; 59 | var dbSetMock = new DbSetMock(null, (x, _) => x.Id); 60 | var dbSet = dbSetMock.Object; 61 | 62 | Assert.That(dbSet.Count(), Is.EqualTo(0)); 63 | 64 | // Act 65 | dbSet.AddRange(users); 66 | ((IDbSetMock)dbSetMock).SaveChanges(); 67 | 68 | // Assert 69 | var firstUser = users.First(); 70 | Assert.That(dbSet.Count(), Is.EqualTo(3)); 71 | Assert.That(dbSet.Any(x => x.Id == firstUser.Id 72 | && x.FullName == firstUser.FullName), Is.True); 73 | } 74 | 75 | [Test] 76 | public async Task DbSetMock_AsyncProvider() 77 | { 78 | var user = new User { Id = Guid.NewGuid(), FullName = "Fake Drake" }; 79 | var dbSetMock = new DbSetMock(null, (x, _) => x.Id); 80 | var dbSet = dbSetMock.Object; 81 | 82 | Assert.That(await dbSet.CountAsync(), Is.EqualTo(0)); 83 | dbSet.Add(user); 84 | Assert.That(await dbSet.CountAsync(), Is.EqualTo(0)); 85 | ((IDbSetMock)dbSetMock).SaveChanges(); 86 | Assert.That(await dbSet.CountAsync(), Is.EqualTo(1)); 87 | Assert.That(await dbSet.AnyAsync(x => x.Id == user.Id && x.FullName == user.FullName), Is.True); 88 | } 89 | 90 | [Test] 91 | public void DbSetMock_GivenEntityIsRemoved_ShouldRemoveAfterCallingSaveChanges() 92 | { 93 | var user = new User { Id = Guid.NewGuid(), FullName = "Fake Drake" }; 94 | var dbSetMock = new DbSetMock(new[] 95 | { 96 | user, 97 | new User {Id = Guid.NewGuid(), FullName = "Jackira Spicy"} 98 | }, (x, _) => x.Id); 99 | var dbSet = dbSetMock.Object; 100 | 101 | Assert.That(dbSet.Count(), Is.EqualTo(2)); 102 | dbSet.Remove(new User { Id = user.Id }); 103 | Assert.That(dbSet.Count(), Is.EqualTo(2)); 104 | Assert.That(dbSet.Any(x => x.Id == user.Id && x.FullName == user.FullName), Is.True); 105 | ((IDbSetMock)dbSetMock).SaveChanges(); 106 | Assert.That(dbSet.Count(), Is.EqualTo(1)); 107 | Assert.That(dbSet.Any(x => x.Id == user.Id && x.FullName == user.FullName), Is.False); 108 | } 109 | 110 | [Test] 111 | public void DbSetMock_GivenRangeOfEntitiesIsRemoved_ShouldRemoveAfterCallingSaveChanges() 112 | { 113 | var users = new[] 114 | { 115 | new User {Id = Guid.NewGuid(), FullName = "User 1"}, 116 | new User {Id = Guid.NewGuid(), FullName = "User 2"}, 117 | new User {Id = Guid.NewGuid(), FullName = "User 3"} 118 | }; 119 | var dbSetMock = new DbSetMock(users, (x, _) => x.Id); 120 | var dbSet = dbSetMock.Object; 121 | 122 | Assert.That(dbSet.Count(), Is.EqualTo(3)); 123 | dbSet.RemoveRange(users.Skip(1)); 124 | Assert.That(dbSet.Count(), Is.EqualTo(3)); 125 | ((IDbSetMock)dbSetMock).SaveChanges(); 126 | Assert.That(dbSet.Count(), Is.EqualTo(1)); 127 | Assert.That(dbSet.Any(x => x.FullName == "User 1"), Is.True); 128 | Assert.That(dbSet.Any(x => x.FullName == "User 2"), Is.False); 129 | } 130 | 131 | [Test] 132 | public void DbSetMock_SaveChanges_GivenEntityPropertyIsChanged_ShouldFireSavedChangesEventWithCorrectUpdatedInfo() 133 | { 134 | var userId = Guid.NewGuid(); 135 | var dbSetMock = new DbSetMock(new[] 136 | { 137 | new User {Id = userId, FullName = "Mark Kramer"}, 138 | new User {Id = Guid.NewGuid(), FullName = "Freddy Kipcurry"} 139 | }, (x, _) => x.Id); 140 | var dbSet = dbSetMock.Object; 141 | 142 | Assert.That(dbSet.Count(), Is.EqualTo(2)); 143 | var fetchedUser = dbSet.First(x => x.Id == userId); 144 | fetchedUser.FullName = "Kramer Mark"; 145 | 146 | SavedChangesEventArgs eventArgs = null; 147 | dbSetMock.SavedChanges += (sender, args) => eventArgs = args; 148 | 149 | ((IDbSetMock)dbSetMock).SaveChanges(); 150 | 151 | Assert.That(eventArgs, Is.Not.Null); 152 | Assert.That(eventArgs.UpdatedEntities, Has.Length.EqualTo(1)); 153 | var updatedEntity = eventArgs.UpdatedEntities[0]; 154 | Assert.That(updatedEntity.UpdatedProperties, Has.Length.EqualTo(1)); 155 | var updatedProperty = updatedEntity.UpdatedProperties[0]; 156 | Assert.That(updatedProperty.Name, Is.EqualTo("FullName")); 157 | Assert.That(updatedProperty.Original, Is.EqualTo("Mark Kramer")); 158 | Assert.That(updatedProperty.New, Is.EqualTo("Kramer Mark")); 159 | } 160 | 161 | [Test] 162 | public void DbSetMock_SaveChanges_GivenEntityPropertyMarkedAsNotMapped_ShouldNotMarkNotMappedPropertyAsModified() 163 | { 164 | var dbSetMock = new DbSetMock(new[] 165 | { 166 | new NestedModel {Id = Guid.NewGuid(), NestedDocument = new NestedModel.Document()}, 167 | new NestedModel {Id = Guid.NewGuid(), NestedDocument = new NestedModel.Document()}, 168 | new NestedModel {Id = Guid.NewGuid(), NestedDocument = new NestedModel.Document()} 169 | }, (x, _) => x.Id); 170 | 171 | SavedChangesEventArgs eventArgs = null; 172 | dbSetMock.SavedChanges += (sender, args) => eventArgs = args; 173 | 174 | dbSetMock.Object.First().Value = "abc"; 175 | 176 | ((IDbSetMock)dbSetMock).SaveChanges(); 177 | 178 | Assert.That(eventArgs, Is.Not.Null); 179 | Assert.That(eventArgs.UpdatedEntities, Has.Length.EqualTo(1)); 180 | Assert.That(eventArgs.UpdatedEntities.First().UpdatedProperties, Has.Length.EqualTo(1)); 181 | var updatedProperty = eventArgs.UpdatedEntities.First().UpdatedProperties.First(); 182 | Assert.That(updatedProperty.Name, Is.EqualTo("Value")); 183 | Assert.That(updatedProperty.Original, Is.EqualTo(null)); 184 | Assert.That(updatedProperty.New, Is.EqualTo("abc")); 185 | } 186 | 187 | [Test] 188 | public void DbSetMock_Empty_AsEnumerable_ShouldReturnEmptyEnumerable() 189 | { 190 | var dbSetMock = new DbSetMock(new List(), (x, _) => x.Id); 191 | var nestedModels = dbSetMock.Object.AsEnumerable(); 192 | Assert.That(nestedModels, Is.Not.Null); 193 | Assert.That(nestedModels, Is.Empty); 194 | } 195 | 196 | [Test] 197 | public void DbSetMock_AsEnumerable_ShouldReturnEnumerableCollection() 198 | { 199 | var dbSetMock = new DbSetMock(new[] 200 | { 201 | new NestedModel {Id = Guid.NewGuid(), NestedDocument = new NestedModel.Document()}, 202 | new NestedModel {Id = Guid.NewGuid(), NestedDocument = new NestedModel.Document()} 203 | }, (x, _) => x.Id); 204 | var nestedModels = dbSetMock.Object.AsEnumerable(); 205 | Assert.That(nestedModels, Is.Not.Null); 206 | Assert.That(nestedModels.Count(), Is.EqualTo(2)); 207 | } 208 | 209 | [Test] 210 | public async Task DbSetMock_AsyncProvider_ShouldReturnRequestedModel() 211 | { 212 | var userId = Guid.NewGuid(); 213 | var dbSetMock = new DbSetMock(new[] 214 | { 215 | new User {Id = userId, FullName = "Mark Kramer"}, 216 | new User {Id = Guid.NewGuid(), FullName = "Freddy Kipcurry"} 217 | }, (x, _) => x.Id); 218 | 219 | var model = await dbSetMock.Object.Where(x => x.Id == userId).FirstOrDefaultAsync(); 220 | Assert.That(model, Is.Not.Null); 221 | Assert.That(model.FullName, Is.EqualTo("Mark Kramer")); 222 | } 223 | 224 | [Test] 225 | public void DbSetMock_Find_ShouldReturnRequestedModel() 226 | { 227 | Guid user1 = Guid.NewGuid(), user2 = Guid.NewGuid(); 228 | var dbSetMock = new DbSetMock(new[] 229 | { 230 | new User {Id = user1, FullName = "Mark Kramer"}, 231 | new User {Id = user2, FullName = "Freddy Kipcurry"} 232 | }, (x, _) => x.Id); 233 | 234 | var result = dbSetMock.Object.Find(user2); 235 | Assert.That(result, Is.Not.Null); 236 | Assert.That(result.FullName, Is.EqualTo("Freddy Kipcurry")); 237 | } 238 | 239 | [Test] 240 | public void DbSetMock_Find_UnknownId_ShouldReturnNull() 241 | { 242 | var unknownUser = Guid.NewGuid(); 243 | var dbSetMock = new DbSetMock(new[] 244 | { 245 | new User {Id = Guid.NewGuid(), FullName = "Mark Kramer"}, 246 | new User {Id = Guid.NewGuid(), FullName = "Freddy Kipcurry"} 247 | }, (x, _) => x.Id); 248 | 249 | var result = dbSetMock.Object.Find(unknownUser); 250 | Assert.That(result, Is.Null); 251 | } 252 | 253 | [Test] 254 | public void DbSetMock_FindOnCompositeTupleKey_ShouldReturnRequestedModel() 255 | { 256 | Guid modelId1 = Guid.NewGuid(), modelId2 = Guid.NewGuid(); 257 | var dbSetMock = new DbSetMock(new[] 258 | { 259 | new NoKeyModel {Id = modelId1, Value = "Value 1"}, 260 | new NoKeyModel {Id = modelId2, Value = "Value 2"} 261 | }, (x, _) => new Tuple(x.Id, x.Value)); 262 | 263 | var result = dbSetMock.Object.Find(modelId2, "Value 2"); 264 | Assert.That(result, Is.Not.Null); 265 | Assert.That(result.Id, Is.EqualTo(modelId2)); 266 | Assert.That(result.Value, Is.EqualTo("Value 2")); 267 | } 268 | 269 | [Test] 270 | public void DbSetMock_FindOnCompositeValueTupleKey_ShouldReturnRequestedModel() 271 | { 272 | Guid modelId1 = Guid.NewGuid(), modelId2 = Guid.NewGuid(); 273 | var dbSetMock = new DbSetMock(new[] 274 | { 275 | new NoKeyModel {Id = modelId1, Value = "Value 1"}, 276 | new NoKeyModel {Id = modelId2, Value = "Value 2"} 277 | }, (x, _) => (x.Id, x.Value)); 278 | 279 | var result = dbSetMock.Object.Find(modelId2, "Value 2"); 280 | Assert.That(result, Is.Not.Null); 281 | Assert.That(result.Id, Is.EqualTo(modelId2)); 282 | Assert.That(result.Value, Is.EqualTo("Value 2")); 283 | } 284 | 285 | [Test] 286 | public void DbSetMock_FetchDataWithAutoMapperProjectTo_ShouldNotThrowException() 287 | { 288 | var dbSetMock = new DbSetMock(new[] 289 | { 290 | new User {Id = Guid.NewGuid(), FullName = "Rita Gesmorov"}, 291 | new User {Id = Guid.NewGuid(), FullName = "Kelly Verbier"} 292 | }, (x, _) => x.Id); 293 | 294 | var mapperConfig = new MapperConfiguration(mapper => mapper.CreateMap()); 295 | 296 | Assert.DoesNotThrowAsync(async () => await dbSetMock.Object 297 | .Where(x => x.FullName.Contains("Rita")) 298 | .ProjectTo(mapperConfig) 299 | .ToListAsync()); 300 | } 301 | 302 | public class NestedModel 303 | { 304 | public Guid Id { get; set; } 305 | 306 | public string Value { get; set; } 307 | 308 | [NotMapped] 309 | public Document NestedDocument 310 | { 311 | get { return new Document { Name = Guid.NewGuid().ToString("N") }; } 312 | // ReSharper disable once ValueParameterNotUsed 313 | set { } 314 | } 315 | 316 | public class Document 317 | { 318 | public string Name { get; set; } 319 | } 320 | } 321 | 322 | [Test] 323 | public void DbSetMock_SaveChanges_GivenAbstractEntityModel_ShouldNotThrowException() 324 | { 325 | var dbSetMock = new DbSetMock(new[] 326 | { 327 | new ConcreteModel {Id = Guid.NewGuid()} 328 | }, (x, _) => x.Id); 329 | 330 | ((IDbSetMock)dbSetMock).SaveChanges(); 331 | } 332 | 333 | public abstract class AbstractModel 334 | { 335 | public Guid Id { get; set; } 336 | } 337 | 338 | public class ConcreteModel : AbstractModel 339 | { 340 | public string Name { get; set; } = "SomeName"; 341 | } 342 | } 343 | } -------------------------------------------------------------------------------- /tests/EntityFrameworkMock.NSubstitute.Tests/EntityFrameworkMock.NSubstitute.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net472;net8.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /tests/EntityFrameworkMock.NSubstitute.Tests/Models/GeneratedGuidKeyModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | using System.ComponentModel.DataAnnotations.Schema; 4 | 5 | namespace EntityFrameworkMock.Tests.Models 6 | { 7 | public class GeneratedGuidKeyModel 8 | { 9 | [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] 10 | public Guid Id { get; set; } 11 | 12 | public string Value { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tests/EntityFrameworkMock.NSubstitute.Tests/Models/GeneratedKeyModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using System.ComponentModel.DataAnnotations.Schema; 3 | 4 | namespace EntityFrameworkMock.Tests.Models 5 | { 6 | public class GeneratedKeyModel 7 | { 8 | [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] 9 | public long Id { get; set; } 10 | 11 | public string Value { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tests/EntityFrameworkMock.NSubstitute.Tests/Models/IntKeyModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using System.ComponentModel.DataAnnotations.Schema; 3 | 4 | namespace EntityFrameworkMock.NSubstitute.Tests.Models 5 | { 6 | [Table("LoggingRepository")] 7 | public class IntKeyModel 8 | { 9 | [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] 10 | public int LoggingRepositoryId { get; set; } 11 | 12 | public string Url { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tests/EntityFrameworkMock.NSubstitute.Tests/Models/NoKeyModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace EntityFrameworkMock.Tests.Models 4 | { 5 | public class NoKeyModel 6 | { 7 | public Guid Id { get; set; } 8 | 9 | public string Value { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tests/EntityFrameworkMock.NSubstitute.Tests/Models/Order.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using System.ComponentModel.DataAnnotations.Schema; 3 | 4 | namespace EntityFrameworkMock.Tests.Models 5 | { 6 | public class Order 7 | { 8 | [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] 9 | public int Id { get; set; } 10 | 11 | public virtual User User { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tests/EntityFrameworkMock.NSubstitute.Tests/Models/User.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | using System.ComponentModel.DataAnnotations.Schema; 4 | 5 | namespace EntityFrameworkMock.Tests.Models 6 | { 7 | public class User 8 | { 9 | [Key, Column(Order = 0)] 10 | public Guid Id { get; set; } 11 | 12 | public string FullName { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tests/EntityFrameworkMock.Shared.Tests/AttributeBasedKeyFactoryBuilderTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | using EntityFrameworkMock.Shared.Tests.Models; 4 | using NUnit.Framework; 5 | 6 | namespace EntityFrameworkMock.Shared.Tests 7 | { 8 | [TestFixture] 9 | public class AttributeBasedKeyFactoryBuilderTests 10 | { 11 | [Test] 12 | public void AttributeBasedKeyFactoryBuilder_GivenModelWithOneKeyProperty_ShouldReturnCorrectKey() 13 | { 14 | var builder = new AttributeBasedKeyFactoryBuilder(); 15 | var factory = builder.BuildKeyFactory(); 16 | var userId = Guid.NewGuid(); 17 | var key = factory(new User {Id = userId, FullName = "Jake Snake"}, null); 18 | Assert.That(key, Is.EqualTo(new Tuple(userId))); 19 | } 20 | 21 | [Test] 22 | public void AttributeBasedKeyFactoryBuilder_GivenModelWithTwoKeyProperties_ShouldReturnCorrectKey() 23 | { 24 | var builder = new AttributeBasedKeyFactoryBuilder(); 25 | var factory = builder.BuildKeyFactory(); 26 | var userId = Guid.NewGuid(); 27 | var tenant = Guid.NewGuid().ToString("N"); 28 | var key = factory(new TenantUser { Id = userId, Tenant = tenant, FullName = "Jake Snake" }, null); 29 | Assert.That(key, Is 30 | .EqualTo(new Tuple(userId, tenant)) 31 | .Or 32 | .EqualTo(new Tuple(tenant, userId))); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/EntityFrameworkMock.Shared.Tests/EntityFrameworkMock.Shared.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net472;net8.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /tests/EntityFrameworkMock.Shared.Tests/KeyContextTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | 3 | namespace EntityFrameworkMock.Shared.Tests 4 | { 5 | [TestFixture] 6 | public class KeyContextTests 7 | { 8 | [Test] 9 | public void NextIdentity_FirstCall_ShouldReturnOne() 10 | { 11 | var keyContext = new KeyContext(); 12 | Assert.That(keyContext.NextIdentity, Is.EqualTo(1)); 13 | } 14 | 15 | [Test] 16 | public void NextIdentity_CallTenTimes_ShouldIncrementEachCall() 17 | { 18 | var keyContext = new KeyContext(); 19 | for (var i = 1; i <= 10; i++) 20 | { 21 | Assert.That(keyContext.NextIdentity, Is.EqualTo(i)); 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/EntityFrameworkMock.Shared.Tests/Models/TenantUser.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using System.ComponentModel.DataAnnotations.Schema; 3 | 4 | namespace EntityFrameworkMock.Shared.Tests.Models 5 | { 6 | public class TenantUser : User 7 | { 8 | [Key, Column(Order = 1), MaxLength(50)] 9 | public string Tenant { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tests/EntityFrameworkMock.Shared.Tests/Models/User.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | using System.ComponentModel.DataAnnotations.Schema; 4 | 5 | namespace EntityFrameworkMock.Shared.Tests.Models 6 | { 7 | public class User 8 | { 9 | [Key, Column(Order = 0)] 10 | public Guid Id { get; set; } 11 | 12 | public string FullName { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tests/EntityFrameworkMock.Shared.Tests/SqlExceptionCreatorTests.cs: -------------------------------------------------------------------------------- 1 | using AutoFixture; 2 | using NUnit.Framework; 3 | 4 | namespace EntityFrameworkMock.Shared.Tests 5 | { 6 | [TestFixture] 7 | public class SqlExceptionCreatorTests 8 | { 9 | [Test] 10 | public void Create_ShouldNotReturnNull() 11 | { 12 | var fixture = new Fixture(); 13 | var message = fixture.Create(); 14 | var errorCode = fixture.Create(); 15 | 16 | System.Data.SqlClient.SqlException exception = SqlExceptionCreator.Create(message, errorCode); 17 | 18 | Assert.That(exception, Is.Not.Null); 19 | } 20 | 21 | [Test] 22 | public void Create_ShouldCreateSqlException() 23 | { 24 | var fixture = new Fixture(); 25 | var message = fixture.Create(); 26 | var errorCode = fixture.Create(); 27 | 28 | System.Data.SqlClient.SqlException exception = SqlExceptionCreator.Create(message, errorCode); 29 | 30 | Assert.That(exception.Message, Is.EqualTo(message)); 31 | Assert.That(exception.Number, Is.EqualTo(errorCode)); 32 | } 33 | } 34 | } 35 | --------------------------------------------------------------------------------