├── .gitignore ├── FunctionApp.Tests.Integration ├── EndToEndCollection.cs ├── EventEndToEndTests.cs ├── FunctionApp.Tests.Integration.csproj ├── HttpEndToEndTests.cs ├── Properties │ └── launchSettings.json └── StorageEndToEndTests.cs ├── FunctionApp.Tests ├── BlobFunction.cs ├── EventHubFunction.cs ├── FunctionApp.Tests.csproj └── HttpFunction.cs ├── FunctionApp ├── .gitignore ├── BlobTrigger.cs ├── EventHubTrigger.cs ├── FunctionApp.csproj ├── HttpTrigger.cs ├── host.json └── local.settings.json.sample ├── FunctionTestHelper.sln ├── FunctionTestHelper ├── EndToEndTestFixture.cs ├── EndToEndTestsBase.cs ├── FunctionTest.cs ├── FunctionTestHelper.csproj └── Helpers │ ├── HttpTestHelpers.cs │ ├── TestFunctionHost.cs │ ├── TestHelpers.cs │ ├── TestLogger.cs │ ├── TestLoggerProvider.cs │ ├── TestLoggerProviderFactory.cs │ └── TestSecretManager.cs ├── LICENSE ├── README.md └── assets └── screenshot.png /.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 | .DS_Store 12 | 13 | .vscode 14 | 15 | # User-specific files (MonoDevelop/Xamarin Studio) 16 | *.userprefs 17 | 18 | # Build results 19 | [Dd]ebug/ 20 | [Dd]ebugPublic/ 21 | [Rr]elease/ 22 | [Rr]eleases/ 23 | x64/ 24 | x86/ 25 | bld/ 26 | [Bb]in/ 27 | [Oo]bj/ 28 | [Ll]og/ 29 | 30 | # Visual Studio 2015/2017 cache/options directory 31 | .vs/ 32 | # Uncomment if you have tasks that create the project's static files in wwwroot 33 | #wwwroot/ 34 | 35 | # Visual Studio 2017 auto generated files 36 | Generated\ Files/ 37 | 38 | # MSTest test Results 39 | [Tt]est[Rr]esult*/ 40 | [Bb]uild[Ll]og.* 41 | 42 | # NUNIT 43 | *.VisualState.xml 44 | TestResult.xml 45 | 46 | # Build Results of an ATL Project 47 | [Dd]ebugPS/ 48 | [Rr]eleasePS/ 49 | dlldata.c 50 | 51 | # Benchmark Results 52 | BenchmarkDotNet.Artifacts/ 53 | 54 | # .NET Core 55 | project.lock.json 56 | project.fragment.lock.json 57 | artifacts/ 58 | 59 | # StyleCop 60 | StyleCopReport.xml 61 | 62 | # Files built by Visual Studio 63 | *_i.c 64 | *_p.c 65 | *_i.h 66 | *.ilk 67 | *.meta 68 | *.obj 69 | *.iobj 70 | *.pch 71 | *.pdb 72 | *.ipdb 73 | *.pgc 74 | *.pgd 75 | *.rsp 76 | *.sbr 77 | *.tlb 78 | *.tli 79 | *.tlh 80 | *.tmp 81 | *.tmp_proj 82 | *.log 83 | *.vspscc 84 | *.vssscc 85 | .builds 86 | *.pidb 87 | *.svclog 88 | *.scc 89 | 90 | # Chutzpah Test files 91 | _Chutzpah* 92 | 93 | # Visual C++ cache files 94 | ipch/ 95 | *.aps 96 | *.ncb 97 | *.opendb 98 | *.opensdf 99 | *.sdf 100 | *.cachefile 101 | *.VC.db 102 | *.VC.VC.opendb 103 | 104 | # Visual Studio profiler 105 | *.psess 106 | *.vsp 107 | *.vspx 108 | *.sap 109 | 110 | # Visual Studio Trace Files 111 | *.e2e 112 | 113 | # TFS 2012 Local Workspace 114 | $tf/ 115 | 116 | # Guidance Automation Toolkit 117 | *.gpState 118 | 119 | # ReSharper is a .NET coding add-in 120 | _ReSharper*/ 121 | *.[Rr]e[Ss]harper 122 | *.DotSettings.user 123 | 124 | # JustCode is a .NET coding add-in 125 | .JustCode 126 | 127 | # TeamCity is a build add-in 128 | _TeamCity* 129 | 130 | # DotCover is a Code Coverage Tool 131 | *.dotCover 132 | 133 | # AxoCover is a Code Coverage Tool 134 | .axoCover/* 135 | !.axoCover/settings.json 136 | 137 | # Visual Studio code coverage results 138 | *.coverage 139 | *.coveragexml 140 | 141 | # NCrunch 142 | _NCrunch_* 143 | .*crunch*.local.xml 144 | nCrunchTemp_* 145 | 146 | # MightyMoose 147 | *.mm.* 148 | AutoTest.Net/ 149 | 150 | # Web workbench (sass) 151 | .sass-cache/ 152 | 153 | # Installshield output folder 154 | [Ee]xpress/ 155 | 156 | # DocProject is a documentation generator add-in 157 | DocProject/buildhelp/ 158 | DocProject/Help/*.HxT 159 | DocProject/Help/*.HxC 160 | DocProject/Help/*.hhc 161 | DocProject/Help/*.hhk 162 | DocProject/Help/*.hhp 163 | DocProject/Help/Html2 164 | DocProject/Help/html 165 | 166 | # Click-Once directory 167 | publish/ 168 | 169 | # Publish Web Output 170 | *.[Pp]ublish.xml 171 | *.azurePubxml 172 | # Note: Comment the next line if you want to checkin your web deploy settings, 173 | # but database connection strings (with potential passwords) will be unencrypted 174 | *.pubxml 175 | *.publishproj 176 | 177 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 178 | # checkin your Azure Web App publish settings, but sensitive information contained 179 | # in these scripts will be unencrypted 180 | PublishScripts/ 181 | 182 | # NuGet Packages 183 | *.nupkg 184 | # The packages folder can be ignored because of Package Restore 185 | **/[Pp]ackages/* 186 | # except build/, which is used as an MSBuild target. 187 | !**/[Pp]ackages/build/ 188 | # Uncomment if necessary however generally it will be regenerated when needed 189 | #!**/[Pp]ackages/repositories.config 190 | # NuGet v3's project.json files produces more ignorable files 191 | *.nuget.props 192 | *.nuget.targets 193 | 194 | # Microsoft Azure Build Output 195 | csx/ 196 | *.build.csdef 197 | 198 | # Microsoft Azure Emulator 199 | ecf/ 200 | rcf/ 201 | 202 | # Windows Store app package directories and files 203 | AppPackages/ 204 | BundleArtifacts/ 205 | Package.StoreAssociation.xml 206 | _pkginfo.txt 207 | *.appx 208 | 209 | # Visual Studio cache files 210 | # files ending in .cache can be ignored 211 | *.[Cc]ache 212 | # but keep track of directories ending in .cache 213 | !*.[Cc]ache/ 214 | 215 | # Others 216 | ClientBin/ 217 | ~$* 218 | *~ 219 | *.dbmdl 220 | *.dbproj.schemaview 221 | *.jfm 222 | *.pfx 223 | *.publishsettings 224 | orleans.codegen.cs 225 | 226 | # Including strong name files can present a security risk 227 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 228 | #*.snk 229 | 230 | # Since there are multiple workflows, uncomment next line to ignore bower_components 231 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 232 | #bower_components/ 233 | 234 | # RIA/Silverlight projects 235 | Generated_Code/ 236 | 237 | # Backup & report files from converting an old project file 238 | # to a newer Visual Studio version. Backup files are not needed, 239 | # because we have git ;-) 240 | _UpgradeReport_Files/ 241 | Backup*/ 242 | UpgradeLog*.XML 243 | UpgradeLog*.htm 244 | ServiceFabricBackup/ 245 | *.rptproj.bak 246 | 247 | # SQL Server files 248 | *.mdf 249 | *.ldf 250 | *.ndf 251 | 252 | # Business Intelligence projects 253 | *.rdl.data 254 | *.bim.layout 255 | *.bim_*.settings 256 | *.rptproj.rsuser 257 | 258 | # Microsoft Fakes 259 | FakesAssemblies/ 260 | 261 | # GhostDoc plugin setting file 262 | *.GhostDoc.xml 263 | 264 | # Node.js Tools for Visual Studio 265 | .ntvs_analysis.dat 266 | node_modules/ 267 | 268 | # Visual Studio 6 build log 269 | *.plg 270 | 271 | # Visual Studio 6 workspace options file 272 | *.opt 273 | 274 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 275 | *.vbw 276 | 277 | # Visual Studio LightSwitch build output 278 | **/*.HTMLClient/GeneratedArtifacts 279 | **/*.DesktopClient/GeneratedArtifacts 280 | **/*.DesktopClient/ModelManifest.xml 281 | **/*.Server/GeneratedArtifacts 282 | **/*.Server/ModelManifest.xml 283 | _Pvt_Extensions 284 | 285 | # Paket dependency manager 286 | .paket/paket.exe 287 | paket-files/ 288 | 289 | # FAKE - F# Make 290 | .fake/ 291 | 292 | # JetBrains Rider 293 | .idea/ 294 | *.sln.iml 295 | 296 | # CodeRush 297 | .cr/ 298 | 299 | # Python Tools for Visual Studio (PTVS) 300 | __pycache__/ 301 | *.pyc 302 | 303 | # Cake - Uncomment if you are using it 304 | # tools/** 305 | # !tools/packages.config 306 | 307 | # Tabs Studio 308 | *.tss 309 | 310 | # Telerik's JustMock configuration file 311 | *.jmconfig 312 | 313 | # BizTalk build output 314 | *.btp.cs 315 | *.btm.cs 316 | *.odx.cs 317 | *.xsd.cs 318 | 319 | # OpenCover UI analysis results 320 | OpenCover/ 321 | 322 | # Azure Stream Analytics local run output 323 | ASALocalRun/ 324 | 325 | # MSBuild Binary and Structured Log 326 | *.binlog 327 | 328 | # NVidia Nsight GPU debugger configuration file 329 | *.nvuser 330 | 331 | # MFractors (Xamarin productivity tool) working folder 332 | .mfractor/ -------------------------------------------------------------------------------- /FunctionApp.Tests.Integration/EndToEndCollection.cs: -------------------------------------------------------------------------------- 1 | using FunctionTestHelper; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using Xunit; 7 | 8 | namespace FunctionApp.Tests.Integration 9 | { 10 | [CollectionDefinition("Function collection")] 11 | public class FunctionCollection : ICollectionFixture 12 | { 13 | // This class has no code, and is never created. Its purpose is simply 14 | // to be the place to apply [CollectionDefinition] and all the 15 | // ICollectionFixture<> interfaces. 16 | 17 | } 18 | 19 | public class TestFixture : EndToEndTestFixture 20 | { 21 | public TestFixture() : 22 | base(@"../../../../FunctionApp/bin/Debug/netstandard2.0", "CSharp") 23 | { 24 | } 25 | 26 | // If desired you can specify which functions load up in this fixture 27 | // protected override IEnumerable GetActiveFunctions() => new[] { "BlobTrigger", "EventTrigger", "HttpTrigger" }; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /FunctionApp.Tests.Integration/EventEndToEndTests.cs: -------------------------------------------------------------------------------- 1 | using FunctionTestHelper; 2 | using Microsoft.Azure.EventHubs; 3 | using Newtonsoft.Json; 4 | using Newtonsoft.Json.Linq; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using Xunit; 10 | using Xunit.Abstractions; 11 | 12 | namespace FunctionApp.Tests.Integration 13 | { 14 | [Collection("Function collection")] 15 | public class EventEndToEndTests : EndToEndTestsBase 16 | { 17 | private readonly ITestOutputHelper output; 18 | public EventEndToEndTests(TestFixture fixture, ITestOutputHelper output) : base(fixture) 19 | { 20 | this.output = output; 21 | } 22 | 23 | [Fact] 24 | public async Task EventHub_TriggerFires() 25 | { 26 | List events = new List(); 27 | string[] ids = new string[3]; 28 | for (int i = 0; i < 3; i++) 29 | { 30 | ids[i] = Guid.NewGuid().ToString(); 31 | JObject jo = new JObject 32 | { 33 | { "value", ids[i] } 34 | }; 35 | var evt = new EventData(Encoding.UTF8.GetBytes(jo.ToString(Formatting.None))); 36 | evt.Properties.Add("TestIndex", i); 37 | events.Add(evt); 38 | } 39 | 40 | string connectionString = Environment.GetEnvironmentVariable("EventHubsConnectionString"); 41 | EventHubsConnectionStringBuilder builder = new EventHubsConnectionStringBuilder(connectionString); 42 | 43 | if (string.IsNullOrWhiteSpace(builder.EntityPath)) 44 | { 45 | string eventHubPath = "test"; 46 | builder.EntityPath = eventHubPath; 47 | } 48 | 49 | EventHubClient eventHubClient = EventHubClient.CreateFromConnectionString(builder.ToString()); 50 | try 51 | { 52 | await eventHubClient.SendAsync(events); 53 | 54 | await WaitForTraceAsync("EventHubTrigger", log => log.FormattedMessage.Contains(ids[2])); 55 | output.WriteLine(Fixture.Host.GetLog()); 56 | } 57 | catch(Exception ex) 58 | { 59 | output.WriteLine(Fixture.Host.GetLog()); 60 | throw ex; 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /FunctionApp.Tests.Integration/FunctionApp.Tests.Integration.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /FunctionApp.Tests.Integration/HttpEndToEndTests.cs: -------------------------------------------------------------------------------- 1 | using FunctionTestHelper; 2 | using Newtonsoft.Json.Linq; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Net; 7 | using System.Net.Http; 8 | using System.Threading.Tasks; 9 | using Xunit; 10 | using Xunit.Abstractions; 11 | 12 | namespace FunctionApp.Tests.Integration 13 | { 14 | [Collection("Function collection")] 15 | public class HttpEndToEndTests : EndToEndTestsBase 16 | { 17 | private readonly ITestOutputHelper output; 18 | public HttpEndToEndTests(TestFixture fixture, ITestOutputHelper output) : base(fixture) 19 | { 20 | this.output = output; 21 | } 22 | 23 | [Fact] 24 | public async Task HttpTrigger_ValidBody() 25 | { 26 | var input = new JObject 27 | { 28 | { "name", "Jeff" } 29 | }; 30 | string key = await Fixture.Host.GetMasterKeyAsync(); 31 | HttpRequestMessage request = new HttpRequestMessage 32 | { 33 | RequestUri = new Uri(string.Format($"http://localhost/api/HttpTrigger?code={key}")), 34 | Method = HttpMethod.Post, 35 | Content = new StringContent(input.ToString()) 36 | }; 37 | HttpResponseMessage response = await Fixture.Host.HttpClient.SendAsync(request); 38 | Assert.Equal(HttpStatusCode.OK, response.StatusCode); 39 | 40 | string body = response.Content.ReadAsStringAsync().Result; 41 | Assert.Equal("Hello, Jeff", body); 42 | output.WriteLine(Fixture.Host.GetLog()); 43 | } 44 | 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /FunctionApp.Tests.Integration/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "FunctionApp.Tests.Integration": { 4 | "commandName": "Project" 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /FunctionApp.Tests.Integration/StorageEndToEndTests.cs: -------------------------------------------------------------------------------- 1 | using FunctionTestHelper; 2 | using Microsoft.WindowsAzure.Storage.Blob; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Xunit; 8 | using Xunit.Abstractions; 9 | 10 | namespace FunctionApp.Tests.Integration 11 | { 12 | [Collection("Function collection")] 13 | public class StorageEndToEndTests : EndToEndTestsBase 14 | { 15 | private readonly ITestOutputHelper output; 16 | public StorageEndToEndTests(TestFixture fixture, ITestOutputHelper output) : base(fixture) 17 | { 18 | this.output = output; 19 | } 20 | 21 | [Fact] 22 | public async Task BlobTrigger_TriggerFires() 23 | { 24 | var container = Fixture.BlobClient.GetContainerReference("test"); 25 | await container.CreateIfNotExistsAsync(); 26 | CloudBlockBlob blob = container.GetBlockBlobReference("testBlob"); 27 | 28 | await blob.UploadTextAsync("Test blob file"); 29 | 30 | await WaitForTraceAsync("BlobTrigger", log => log.FormattedMessage.Contains("testBlob")); 31 | 32 | // clean up 33 | await blob.DeleteIfExistsAsync(); 34 | 35 | output.WriteLine(Fixture.Host.GetLog()); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /FunctionApp.Tests/BlobFunction.cs: -------------------------------------------------------------------------------- 1 | using FunctionTestHelper; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.Azure.EventHubs; 4 | using Microsoft.Extensions.Primitives; 5 | using Newtonsoft.Json; 6 | using Newtonsoft.Json.Linq; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.IO; 10 | using System.Text; 11 | using System.Threading.Tasks; 12 | using Xunit; 13 | 14 | 15 | namespace FunctionApp.Tests 16 | { 17 | public class BlobFunction : FunctionTest 18 | { 19 | [Fact] 20 | public async Task BlobFunction_ValidStreamAndName() 21 | { 22 | Stream s = new MemoryStream(); 23 | using(StreamWriter sw = new StreamWriter(s)) 24 | { 25 | await sw.WriteLineAsync("This is a test"); 26 | BlobTrigger.Run(s, "testBlob", log); 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /FunctionApp.Tests/EventHubFunction.cs: -------------------------------------------------------------------------------- 1 | using FunctionTestHelper; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.Azure.EventHubs; 4 | using Microsoft.Extensions.Primitives; 5 | using Newtonsoft.Json; 6 | using Newtonsoft.Json.Linq; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Text; 10 | using Xunit; 11 | 12 | 13 | namespace FunctionApp.Tests 14 | { 15 | public class EventHubFunction : FunctionTest 16 | { 17 | [Fact] 18 | public void EventHubFunction_ValidArray() 19 | { 20 | List events = new List(); 21 | string[] ids = new string[3]; 22 | for (int i = 0; i < 3; i++) 23 | { 24 | ids[i] = Guid.NewGuid().ToString(); 25 | JObject jo = new JObject 26 | { 27 | { "value", ids[i] } 28 | }; 29 | var evt = new EventData(Encoding.UTF8.GetBytes(jo.ToString(Formatting.None))); 30 | evt.Properties.Add("TestIndex", i); 31 | events.Add(evt); 32 | } 33 | EventHubTrigger.Run(events.ToArray(), log: log); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /FunctionApp.Tests/FunctionApp.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /FunctionApp.Tests/HttpFunction.cs: -------------------------------------------------------------------------------- 1 | using FunctionTestHelper; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.Extensions.Primitives; 4 | using System; 5 | using System.Collections.Generic; 6 | using Xunit; 7 | 8 | namespace FunctionApp.Tests 9 | { 10 | public class HttpFunction : FunctionTest 11 | { 12 | [Fact] 13 | public void HttpTrigger_ValidBody() 14 | { 15 | var result = HttpTrigger.Run( 16 | req: HttpTestHelpers.CreateHttpRequest("POST", uriString: "http://localhost", body: new { name = "Jeff"}), 17 | log: log); 18 | 19 | var resultObject = (OkObjectResult)result; 20 | Assert.Equal("Hello, Jeff", resultObject.Value); 21 | } 22 | 23 | [Fact] 24 | public void HttpTrigger_ValidQuery() 25 | { 26 | var result = HttpTrigger.Run( 27 | req: HttpTestHelpers.CreateHttpRequest("POST", uriString: "http://localhost?name=Jeff"), 28 | log: log); 29 | 30 | var resultObject = (OkObjectResult)result; 31 | Assert.Equal("Hello, Jeff", resultObject.Value); 32 | } 33 | 34 | [Fact] 35 | public void HttpTrigger_EmptyBodyAndQuery() 36 | { 37 | var result = HttpTrigger.Run( 38 | req: HttpTestHelpers.CreateHttpRequest("POST", uriString: "http://localhost"), 39 | log: log); 40 | 41 | Assert.IsType(result); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /FunctionApp/.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # Azure Functions localsettings file 5 | local.settings.json 6 | 7 | # User-specific files 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | bld/ 24 | [Bb]in/ 25 | [Oo]bj/ 26 | [Ll]og/ 27 | 28 | # Visual Studio 2015 cache/options directory 29 | .vs/ 30 | # Uncomment if you have tasks that create the project's static files in wwwroot 31 | #wwwroot/ 32 | 33 | # MSTest test Results 34 | [Tt]est[Rr]esult*/ 35 | [Bb]uild[Ll]og.* 36 | 37 | # NUNIT 38 | *.VisualState.xml 39 | TestResult.xml 40 | 41 | # Build Results of an ATL Project 42 | [Dd]ebugPS/ 43 | [Rr]eleasePS/ 44 | dlldata.c 45 | 46 | # DNX 47 | project.lock.json 48 | project.fragment.lock.json 49 | artifacts/ 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 | # NCrunch 117 | _NCrunch_* 118 | .*crunch*.local.xml 119 | nCrunchTemp_* 120 | 121 | # MightyMoose 122 | *.mm.* 123 | AutoTest.Net/ 124 | 125 | # Web workbench (sass) 126 | .sass-cache/ 127 | 128 | # Installshield output folder 129 | [Ee]xpress/ 130 | 131 | # DocProject is a documentation generator add-in 132 | DocProject/buildhelp/ 133 | DocProject/Help/*.HxT 134 | DocProject/Help/*.HxC 135 | DocProject/Help/*.hhc 136 | DocProject/Help/*.hhk 137 | DocProject/Help/*.hhp 138 | DocProject/Help/Html2 139 | DocProject/Help/html 140 | 141 | # Click-Once directory 142 | publish/ 143 | 144 | # Publish Web Output 145 | *.[Pp]ublish.xml 146 | *.azurePubxml 147 | # TODO: Comment the next line if you want to checkin your web deploy settings 148 | # but database connection strings (with potential passwords) will be unencrypted 149 | #*.pubxml 150 | *.publishproj 151 | 152 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 153 | # checkin your Azure Web App publish settings, but sensitive information contained 154 | # in these scripts will be unencrypted 155 | PublishScripts/ 156 | 157 | # NuGet Packages 158 | *.nupkg 159 | # The packages folder can be ignored because of Package Restore 160 | **/packages/* 161 | # except build/, which is used as an MSBuild target. 162 | !**/packages/build/ 163 | # Uncomment if necessary however generally it will be regenerated when needed 164 | #!**/packages/repositories.config 165 | # NuGet v3's project.json files produces more ignoreable files 166 | *.nuget.props 167 | *.nuget.targets 168 | 169 | # Microsoft Azure Build Output 170 | csx/ 171 | *.build.csdef 172 | 173 | # Microsoft Azure Emulator 174 | ecf/ 175 | rcf/ 176 | 177 | # Windows Store app package directories and files 178 | AppPackages/ 179 | BundleArtifacts/ 180 | Package.StoreAssociation.xml 181 | _pkginfo.txt 182 | 183 | # Visual Studio cache files 184 | # files ending in .cache can be ignored 185 | *.[Cc]ache 186 | # but keep track of directories ending in .cache 187 | !*.[Cc]ache/ 188 | 189 | # Others 190 | ClientBin/ 191 | ~$* 192 | *~ 193 | *.dbmdl 194 | *.dbproj.schemaview 195 | *.jfm 196 | *.pfx 197 | *.publishsettings 198 | node_modules/ 199 | orleans.codegen.cs 200 | 201 | # Since there are multiple workflows, uncomment next line to ignore bower_components 202 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 203 | #bower_components/ 204 | 205 | # RIA/Silverlight projects 206 | Generated_Code/ 207 | 208 | # Backup & report files from converting an old project file 209 | # to a newer Visual Studio version. Backup files are not needed, 210 | # because we have git ;-) 211 | _UpgradeReport_Files/ 212 | Backup*/ 213 | UpgradeLog*.XML 214 | UpgradeLog*.htm 215 | 216 | # SQL Server files 217 | *.mdf 218 | *.ldf 219 | 220 | # Business Intelligence projects 221 | *.rdl.data 222 | *.bim.layout 223 | *.bim_*.settings 224 | 225 | # Microsoft Fakes 226 | FakesAssemblies/ 227 | 228 | # GhostDoc plugin setting file 229 | *.GhostDoc.xml 230 | 231 | # Node.js Tools for Visual Studio 232 | .ntvs_analysis.dat 233 | 234 | # Visual Studio 6 build log 235 | *.plg 236 | 237 | # Visual Studio 6 workspace options file 238 | *.opt 239 | 240 | # Visual Studio LightSwitch build output 241 | **/*.HTMLClient/GeneratedArtifacts 242 | **/*.DesktopClient/GeneratedArtifacts 243 | **/*.DesktopClient/ModelManifest.xml 244 | **/*.Server/GeneratedArtifacts 245 | **/*.Server/ModelManifest.xml 246 | _Pvt_Extensions 247 | 248 | # Paket dependency manager 249 | .paket/paket.exe 250 | paket-files/ 251 | 252 | # FAKE - F# Make 253 | .fake/ 254 | 255 | # JetBrains Rider 256 | .idea/ 257 | *.sln.iml 258 | 259 | # CodeRush 260 | .cr/ 261 | 262 | # Python Tools for Visual Studio (PTVS) 263 | __pycache__/ 264 | *.pyc -------------------------------------------------------------------------------- /FunctionApp/BlobTrigger.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Microsoft.Azure.WebJobs; 3 | using Microsoft.Azure.WebJobs.Host; 4 | using Microsoft.Extensions.Logging; 5 | 6 | namespace FunctionApp 7 | { 8 | public static class BlobTrigger 9 | { 10 | [FunctionName("BlobTrigger")] 11 | public static void Run([BlobTrigger("test/{name}", Connection = "AzureWebJobsStorage")]Stream myBlob, string name, ILogger log) 12 | { 13 | log.LogInformation($"C# Blob trigger function Processed blob\n Name:{name} \n Size: {myBlob.Length} Bytes"); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /FunctionApp/EventHubTrigger.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Azure.EventHubs; 2 | using Microsoft.Azure.WebJobs; 3 | using Microsoft.Azure.WebJobs.Host; 4 | using Microsoft.Azure.WebJobs.ServiceBus; 5 | using Microsoft.Extensions.Logging; 6 | using System.Text; 7 | 8 | namespace FunctionApp 9 | { 10 | public static class EventHubTrigger 11 | { 12 | [FunctionName("EventHubTrigger")] 13 | public static void Run([EventHubTrigger("test", Connection = "EventHubsConnectionString")]EventData[] eventDataArray, ILogger log) 14 | { 15 | foreach(var e in eventDataArray) 16 | { 17 | log.LogInformation($"C# Event Hub trigger function processed a message: {Encoding.UTF8.GetString(e.Body.Array)}"); 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /FunctionApp/FunctionApp.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netstandard2.0 4 | v2 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | PreserveNewest 13 | 14 | 15 | PreserveNewest 16 | Never 17 | 18 | 19 | -------------------------------------------------------------------------------- /FunctionApp/HttpTrigger.cs: -------------------------------------------------------------------------------- 1 | 2 | using System.IO; 3 | using Microsoft.AspNetCore.Mvc; 4 | using Microsoft.Azure.WebJobs; 5 | using Microsoft.Azure.WebJobs.Extensions.Http; 6 | using Microsoft.AspNetCore.Http; 7 | using Newtonsoft.Json; 8 | using Microsoft.Extensions.Logging; 9 | 10 | namespace FunctionApp 11 | { 12 | public static class HttpTrigger 13 | { 14 | [FunctionName("HttpTrigger")] 15 | public static IActionResult Run([HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)]HttpRequest req, ILogger log) 16 | { 17 | log.LogInformation("C# HTTP trigger function processed a request."); 18 | 19 | string name = req.Query["name"]; 20 | 21 | string requestBody = new StreamReader(req.Body).ReadToEnd(); 22 | dynamic data = JsonConvert.DeserializeObject(requestBody); 23 | name = name ?? data?.name; 24 | 25 | return name != null 26 | ? (ActionResult)new OkObjectResult($"Hello, {name}") 27 | : new BadRequestObjectResult("Please pass a name on the query string or in the request body"); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /FunctionApp/host.json: -------------------------------------------------------------------------------- 1 | { 2 | } -------------------------------------------------------------------------------- /FunctionApp/local.settings.json.sample: -------------------------------------------------------------------------------- 1 | { 2 | "IsEncrypted": false, 3 | "Values": { 4 | "AzureWebJobsStorage": "{Azure Storage Connection String}", 5 | "AzureWebJobsDashboard": "", 6 | "EventHubsConnectionString": "{Event Hubs Namespace Connection String}" 7 | } 8 | } -------------------------------------------------------------------------------- /FunctionTestHelper.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27729.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FunctionTestHelper", "FunctionTestHelper\FunctionTestHelper.csproj", "{6B8D529F-6AA1-4041-ABD0-491A26488BD4}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FunctionApp", "FunctionApp\FunctionApp.csproj", "{13F209AA-3487-469E-A0FE-94EC3629CBB0}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FunctionApp.Tests", "FunctionApp.Tests\FunctionApp.Tests.csproj", "{C57928F3-BFED-4230-9059-8BB1208764D7}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FunctionApp.Tests.Integration", "FunctionApp.Tests.Integration\FunctionApp.Tests.Integration.csproj", "{56C3AA8E-2EE5-481A-AD9E-6AE90F0EE928}" 13 | EndProject 14 | Global 15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 16 | Debug|Any CPU = Debug|Any CPU 17 | Release|Any CPU = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {6B8D529F-6AA1-4041-ABD0-491A26488BD4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {6B8D529F-6AA1-4041-ABD0-491A26488BD4}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {6B8D529F-6AA1-4041-ABD0-491A26488BD4}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {6B8D529F-6AA1-4041-ABD0-491A26488BD4}.Release|Any CPU.Build.0 = Release|Any CPU 24 | {13F209AA-3487-469E-A0FE-94EC3629CBB0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {13F209AA-3487-469E-A0FE-94EC3629CBB0}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {13F209AA-3487-469E-A0FE-94EC3629CBB0}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {13F209AA-3487-469E-A0FE-94EC3629CBB0}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {C57928F3-BFED-4230-9059-8BB1208764D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {C57928F3-BFED-4230-9059-8BB1208764D7}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {C57928F3-BFED-4230-9059-8BB1208764D7}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {C57928F3-BFED-4230-9059-8BB1208764D7}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {56C3AA8E-2EE5-481A-AD9E-6AE90F0EE928}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {56C3AA8E-2EE5-481A-AD9E-6AE90F0EE928}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {56C3AA8E-2EE5-481A-AD9E-6AE90F0EE928}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {56C3AA8E-2EE5-481A-AD9E-6AE90F0EE928}.Release|Any CPU.Build.0 = Release|Any CPU 36 | EndGlobalSection 37 | GlobalSection(SolutionProperties) = preSolution 38 | HideSolutionNode = FALSE 39 | EndGlobalSection 40 | GlobalSection(ExtensibilityGlobals) = postSolution 41 | SolutionGuid = {C95ED63D-8E50-4C35-904E-E4AC4DC9CFB3} 42 | EndGlobalSection 43 | EndGlobal 44 | -------------------------------------------------------------------------------- /FunctionTestHelper/EndToEndTestFixture.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Threading.Tasks; 9 | using Microsoft.Azure.WebJobs; 10 | using Microsoft.Azure.WebJobs.Host; 11 | using Microsoft.Azure.WebJobs.Script; 12 | using Microsoft.WindowsAzure.Storage; 13 | using Microsoft.WindowsAzure.Storage.Blob; 14 | using Microsoft.WindowsAzure.Storage.Queue; 15 | using Microsoft.WindowsAzure.Storage.Table; 16 | using Newtonsoft.Json.Linq; 17 | 18 | namespace FunctionTestHelper 19 | { 20 | public abstract class EndToEndTestFixture : IDisposable 21 | { 22 | private string _copiedRootPath; 23 | 24 | protected EndToEndTestFixture(string rootPath, string testId, string extensionName = null, string extensionVersion = null) 25 | { 26 | FixtureId = testId; 27 | 28 | _copiedRootPath = Path.Combine(Path.GetTempPath(), "FunctionsE2E", DateTime.UtcNow.ToString("yyMMdd-HHmmss")); 29 | FileUtility.CopyDirectory(rootPath, _copiedRootPath); 30 | 31 | // Allow derived classes to limit functions. We'll update host.json in the copied location 32 | // so it only affects this fixture's instance. 33 | IEnumerable functions = GetActiveFunctions(); 34 | string hostJsonPath = Path.Combine(_copiedRootPath, "host.json"); 35 | JObject hostJson = JObject.Parse(File.ReadAllText(hostJsonPath)); 36 | if (functions != null && functions.Any()) 37 | { 38 | hostJson["functions"] = JArray.FromObject(functions); 39 | } 40 | File.WriteAllText(hostJsonPath, hostJson.ToString()); 41 | 42 | UpdateEnvironmentVariables(Path.Combine(_copiedRootPath, "local.settings.json")); 43 | 44 | string connectionString = AmbientConnectionStringProvider.Instance.GetConnectionString(ConnectionStringNames.Storage); 45 | CloudStorageAccount storageAccount = CloudStorageAccount.Parse(connectionString); 46 | 47 | QueueClient = storageAccount.CreateCloudQueueClient(); 48 | BlobClient = storageAccount.CreateCloudBlobClient(); 49 | TableClient = storageAccount.CreateCloudTableClient(); 50 | 51 | Host = new TestFunctionHost(_copiedRootPath); 52 | 53 | // We can currently only support a single extension. 54 | if (extensionName != null && extensionVersion != null) 55 | { 56 | Host.SetNugetPackageSources("http://www.myget.org/F/azure-appservice/api/v2", "https://api.nuget.org/v3/index.json"); 57 | Host.InstallBindingExtension(extensionName, extensionVersion).Wait(TimeSpan.FromSeconds(30)); 58 | } 59 | 60 | Host.StartAsync().Wait(TimeSpan.FromSeconds(30)); 61 | } 62 | 63 | private void UpdateEnvironmentVariables(string secretsPath) 64 | { 65 | var localSettings = File.ReadAllText(secretsPath); 66 | JObject settingValues = JObject.Parse(localSettings)["Values"] as JObject; 67 | foreach (var secret in settingValues) 68 | { 69 | if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable(secret.Key))) 70 | { 71 | Environment.SetEnvironmentVariable(secret.Key, (string)secret.Value, EnvironmentVariableTarget.Process); 72 | } 73 | } 74 | } 75 | 76 | public CloudQueueClient QueueClient { get; private set; } 77 | 78 | public CloudTableClient TableClient { get; private set; } 79 | 80 | public CloudBlobClient BlobClient { get; private set; } 81 | 82 | public TestFunctionHost Host { get; private set; } 83 | 84 | public string FixtureId { get; private set; } 85 | 86 | /// 87 | /// Override this to set the list of functions to write to host.json. 88 | /// 89 | /// The list of enabled functions. 90 | protected virtual IEnumerable GetActiveFunctions() 91 | { 92 | return Enumerable.Empty(); 93 | } 94 | 95 | 96 | 97 | public virtual void Dispose() 98 | { 99 | Host?.Dispose(); 100 | 101 | // Clean up all but the last 5 directories for debugging failures. 102 | var directoriesToDelete = Directory.EnumerateDirectories(Path.GetDirectoryName(_copiedRootPath)) 103 | .OrderByDescending(p => p) 104 | .Skip(5); 105 | 106 | foreach (string directory in directoriesToDelete) 107 | { 108 | try 109 | { 110 | Directory.Delete(directory, true); 111 | } 112 | catch 113 | { 114 | // best effort 115 | } 116 | } 117 | } 118 | } 119 | } -------------------------------------------------------------------------------- /FunctionTestHelper/EndToEndTestsBase.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Data.Common; 7 | using System.Linq; 8 | using System.Net; 9 | using System.Threading.Tasks; 10 | using Microsoft.Azure.WebJobs; 11 | using Microsoft.Azure.WebJobs.Host; 12 | using Microsoft.Azure.WebJobs.Logging; 13 | using Microsoft.Azure.WebJobs.Script.Config; 14 | using Microsoft.Extensions.Logging; 15 | using Microsoft.WindowsAzure.Storage.Blob; 16 | using Microsoft.WindowsAzure.Storage.Queue; 17 | using Microsoft.WindowsAzure.Storage.Table; 18 | using Newtonsoft.Json; 19 | using Newtonsoft.Json.Linq; 20 | using Xunit; 21 | 22 | namespace FunctionTestHelper 23 | { 24 | public abstract class EndToEndTestsBase : 25 | IClassFixture where TTestFixture : EndToEndTestFixture, new() 26 | { 27 | private INameResolver _nameResolver = new DefaultNameResolver(); 28 | private static readonly ScriptSettingsManager SettingsManager = ScriptSettingsManager.Instance; 29 | 30 | public EndToEndTestsBase(TTestFixture fixture) 31 | { 32 | Fixture = fixture; 33 | } 34 | 35 | protected TTestFixture Fixture { get; private set; } 36 | protected async Task GetFunctionTestResult(string functionName) 37 | { 38 | string logEntry = null; 39 | 40 | await TestHelpers.Await(() => 41 | { 42 | // search the logs for token "TestResult:" and parse the following JSON 43 | var logs = Fixture.Host.GetLogMessages(LogCategories.CreateFunctionUserCategory(functionName)); 44 | if (logs != null) 45 | { 46 | logEntry = logs.Select(p => p.FormattedMessage).SingleOrDefault(p => p != null && p.Contains("TestResult:")); 47 | } 48 | return logEntry != null; 49 | }); 50 | 51 | int idx = logEntry.IndexOf("{"); 52 | logEntry = logEntry.Substring(idx); 53 | 54 | return JObject.Parse(logEntry); 55 | } 56 | 57 | protected async Task WaitForTraceAsync(string functionName, Func filter, int timeout) 58 | { 59 | LogMessage logMessage = null; 60 | 61 | await TestHelpers.Await(() => 62 | { 63 | logMessage = Fixture.Host.GetLogMessages(LogCategories.CreateFunctionUserCategory(functionName)).SingleOrDefault(filter); 64 | return logMessage != null; 65 | }, timeout); 66 | 67 | return logMessage; 68 | } 69 | 70 | protected async Task WaitForTraceAsync(string functionName, Func filter) 71 | { 72 | return await WaitForTraceAsync(functionName, filter, 30000); 73 | } 74 | 75 | protected async Task WaitForTraceAsync(Func filter) 76 | { 77 | LogMessage logMessage = null; 78 | 79 | await TestHelpers.Await(() => 80 | { 81 | logMessage = Fixture.Host.GetLogMessages().SingleOrDefault(filter); 82 | return logMessage != null; 83 | }); 84 | 85 | return logMessage; 86 | } 87 | 88 | } 89 | } -------------------------------------------------------------------------------- /FunctionTestHelper/FunctionTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using Microsoft.AspNetCore.Http.Internal; 3 | using Microsoft.Azure.WebJobs.Host; 4 | using Microsoft.Extensions.Primitives; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Diagnostics; 8 | using System.IO; 9 | using System.Text; 10 | 11 | namespace FunctionTestHelper 12 | { 13 | public abstract class FunctionTest 14 | { 15 | public TestLogger log = new TestLogger("Test"); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /FunctionTestHelper/FunctionTestHelper.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | NU1701 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /FunctionTestHelper/Helpers/HttpTestHelpers.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections; 6 | using System.IO; 7 | using System.Text; 8 | using Microsoft.AspNetCore.Http; 9 | using Microsoft.AspNetCore.Http.Features; 10 | using Newtonsoft.Json.Linq; 11 | 12 | namespace FunctionTestHelper 13 | { 14 | public class HttpTestHelpers 15 | { 16 | public static HttpRequest CreateHttpRequest(string method, string uriString, IHeaderDictionary headers = null, object body = null) 17 | { 18 | var uri = new Uri(uriString); 19 | var request = new DefaultHttpContext().Request; 20 | var requestFeature = request.HttpContext.Features.Get(); 21 | requestFeature.Method = method; 22 | requestFeature.Scheme = uri.Scheme; 23 | requestFeature.Path = uri.GetComponents(UriComponents.KeepDelimiter | UriComponents.Path, UriFormat.Unescaped); 24 | requestFeature.PathBase = string.Empty; 25 | requestFeature.QueryString = uri.GetComponents(UriComponents.KeepDelimiter | UriComponents.Query, UriFormat.Unescaped); 26 | 27 | headers = headers ?? new HeaderDictionary(); 28 | 29 | if (!string.IsNullOrEmpty(uri.Host)) 30 | { 31 | headers.Add("Host", uri.Host); 32 | } 33 | 34 | if (body != null) 35 | { 36 | byte[] bytes = null; 37 | if (body is string bodyString) 38 | { 39 | bytes = Encoding.UTF8.GetBytes(bodyString); 40 | } 41 | else if (body is byte[] bodyBytes) 42 | { 43 | bytes = bodyBytes; 44 | } 45 | else if (body is IEnumerable bodyArray) 46 | { 47 | bytes = Encoding.UTF8.GetBytes(JArray.FromObject(bodyArray).ToString(Newtonsoft.Json.Formatting.None)); 48 | } 49 | else if (body is object bodyObject) 50 | { 51 | bytes = Encoding.UTF8.GetBytes(JObject.FromObject(bodyObject).ToString(Newtonsoft.Json.Formatting.None)); 52 | } 53 | 54 | 55 | requestFeature.Body = new MemoryStream(bytes); 56 | request.ContentLength = request.Body.Length; 57 | headers.Add("Content-Length", request.Body.Length.ToString()); 58 | } 59 | 60 | requestFeature.Headers = headers; 61 | 62 | return request; 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /FunctionTestHelper/Helpers/TestFunctionHost.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Net; 7 | using System.Net.Http; 8 | using System.Text; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | using System.Xml; 12 | using Microsoft.AspNetCore.Hosting; 13 | using Microsoft.AspNetCore.TestHost; 14 | using Microsoft.Azure.WebJobs; 15 | using Microsoft.Azure.WebJobs.Script; 16 | using Microsoft.Azure.WebJobs.Script.WebHost; 17 | using Microsoft.Azure.WebJobs.Script.WebHost.Models; 18 | using Microsoft.Extensions.DependencyInjection; 19 | using Microsoft.Extensions.DependencyInjection.Extensions; 20 | using Newtonsoft.Json.Linq; 21 | 22 | namespace FunctionTestHelper 23 | { 24 | public class TestFunctionHost : IDisposable 25 | { 26 | private readonly WebHostSettings _hostSettings; 27 | private readonly TestServer _testServer; 28 | private readonly string _appRoot; 29 | private readonly TestLoggerProvider _loggerProvider = new TestLoggerProvider(); 30 | 31 | public TestFunctionHost(string appRoot) 32 | { 33 | _appRoot = appRoot; 34 | 35 | _hostSettings = new WebHostSettings 36 | { 37 | IsSelfHost = true, 38 | ScriptPath = _appRoot, 39 | LogPath = Path.Combine(Path.GetTempPath(), @"Functions"), 40 | SecretsPath = Environment.CurrentDirectory // not used 41 | }; 42 | 43 | _testServer = new TestServer( 44 | Microsoft.AspNetCore.WebHost.CreateDefaultBuilder() 45 | .UseStartup() 46 | .ConfigureServices(services => 47 | { 48 | services.Replace(new ServiceDescriptor(typeof(WebHostSettings), _hostSettings)); 49 | services.Replace(new ServiceDescriptor(typeof(ILoggerProviderFactory), new TestLoggerProviderFactory(_loggerProvider, includeDefaultLoggerProviders: false))); 50 | services.Replace(new ServiceDescriptor(typeof(ISecretManager), new TestSecretManager())); 51 | })); 52 | 53 | HttpClient = new HttpClient(new UpdateContentLengthHandler(_testServer.CreateHandler())); 54 | HttpClient.BaseAddress = new Uri("https://localhost/"); 55 | } 56 | 57 | public ScriptHostConfiguration ScriptConfig => _testServer.Host.Services.GetService().GetScriptHostConfiguration(_hostSettings); 58 | 59 | public ISecretManager SecretManager => _testServer.Host.Services.GetService(); 60 | 61 | public string LogPath => _hostSettings.LogPath; 62 | 63 | public string ScriptPath => _hostSettings.ScriptPath; 64 | 65 | public async Task GetMasterKeyAsync() 66 | { 67 | HostSecretsInfo secrets = await SecretManager.GetHostSecretsAsync(); 68 | return secrets.MasterKey; 69 | } 70 | 71 | public async Task GetFunctionSecretAsync(string functionName) 72 | { 73 | var secrets = await SecretManager.GetFunctionSecretsAsync(functionName); 74 | return secrets.First().Value; 75 | } 76 | 77 | public HttpClient HttpClient { get; private set; } 78 | 79 | public async Task StartAsync() 80 | { 81 | bool running = false; 82 | while (!running) 83 | { 84 | running = await IsHostRunning(HttpClient); 85 | 86 | if (!running) 87 | { 88 | await Task.Delay(500); 89 | } 90 | } 91 | } 92 | 93 | public void SetNugetPackageSources(params string[] sources) 94 | { 95 | XmlWriterSettings settings = new XmlWriterSettings(); 96 | settings.Indent = true; 97 | 98 | using (XmlWriter writer = XmlWriter.Create(Path.Combine(_appRoot, "nuget.config"), settings)) 99 | { 100 | writer.WriteStartElement("configuration"); 101 | writer.WriteStartElement("packageSources"); 102 | for (int i = 0; i < sources.Length; i++) 103 | { 104 | writer.WriteStartElement("add"); 105 | writer.WriteAttributeString("key", $"source{i}"); 106 | writer.WriteAttributeString("value", sources[i]); 107 | writer.WriteEndElement(); 108 | } 109 | writer.WriteEndElement(); 110 | writer.WriteEndElement(); 111 | } 112 | } 113 | 114 | public IEnumerable GetLogMessages() => _loggerProvider.GetAllLogMessages(); 115 | 116 | public IEnumerable GetLogMessages(string category) => GetLogMessages().Where(p => p.Category == category); 117 | 118 | public string GetLog() => string.Join(Environment.NewLine, GetLogMessages()); 119 | 120 | public void ClearLogMessages() => _loggerProvider.ClearAllLogMessages(); 121 | 122 | public async Task BeginFunctionAsync(string functionName, JToken payload) 123 | { 124 | JObject wrappedPayload = new JObject 125 | { 126 | { "input", payload.ToString() } 127 | }; 128 | 129 | HostSecretsInfo secrets = await SecretManager.GetHostSecretsAsync(); 130 | string uri = $"admin/functions/{functionName}?code={secrets.MasterKey}"; 131 | HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, uri); 132 | request.Content = new StringContent(wrappedPayload.ToString(), Encoding.UTF8, "application/json"); 133 | HttpResponseMessage response = await HttpClient.SendAsync(request); 134 | response.EnsureSuccessStatusCode(); 135 | } 136 | 137 | public async Task InstallBindingExtension(string packageName, string packageVersion) 138 | { 139 | HostSecretsInfo secrets = await SecretManager.GetHostSecretsAsync(); 140 | string uri = $"admin/host/extensions?code={secrets.MasterKey}"; 141 | HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, uri); 142 | 143 | string payload = new JObject 144 | { 145 | { "id", packageName }, 146 | {"version", packageVersion } 147 | }.ToString(Newtonsoft.Json.Formatting.None); 148 | 149 | request.Content = new StringContent(payload, Encoding.UTF8, "application/json"); 150 | var response = await HttpClient.SendAsync(request); 151 | var jobStatusUri = response.Headers.Location; 152 | string status = null; 153 | do 154 | { 155 | await Task.Delay(500); 156 | response = await CheckExtensionInstallStatus(jobStatusUri); 157 | var jobStatus = await response.Content.ReadAsAsync(); 158 | status = jobStatus["status"].ToString(); 159 | } while (status == "Started"); 160 | 161 | if (status != "Succeeded") 162 | { 163 | throw new InvalidOperationException("Failed to install extension."); 164 | } 165 | 166 | // TODO: Find a better way to ensure the site has restarted. 167 | await Task.Delay(3000); 168 | } 169 | 170 | private async Task CheckExtensionInstallStatus(Uri jobLocation) 171 | { 172 | HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, jobLocation); 173 | return await HttpClient.SendAsync(request); 174 | } 175 | 176 | public async Task GetFunctionStatusAsync(string functionName) 177 | { 178 | HostSecretsInfo secrets = await SecretManager.GetHostSecretsAsync(); 179 | string uri = $"admin/functions/{functionName}/status?code={secrets.MasterKey}"; 180 | HttpResponseMessage response = await HttpClient.GetAsync(uri); 181 | response.EnsureSuccessStatusCode(); 182 | return await response.Content.ReadAsAsync(); 183 | } 184 | 185 | public async Task GetHostStatusAsync() 186 | { 187 | HostSecretsInfo secrets = await SecretManager.GetHostSecretsAsync(); 188 | string uri = $"admin/host/status?code={secrets.MasterKey}"; 189 | HttpResponseMessage response = await HttpClient.GetAsync(uri); 190 | response.EnsureSuccessStatusCode(); 191 | return await response.Content.ReadAsAsync(); 192 | } 193 | 194 | public void Dispose() 195 | { 196 | HttpClient.Dispose(); 197 | _testServer.Dispose(); 198 | } 199 | 200 | private async Task IsHostRunning(HttpClient client) 201 | { 202 | HostSecretsInfo secrets = await SecretManager.GetHostSecretsAsync(); 203 | 204 | // Workaround for https://github.com/Azure/azure-functions-host/issues/2397 as the base URL 205 | // doesn't currently start the host. 206 | // Note: the master key "1234" is from the TestSecretManager. 207 | using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, $"/admin/functions/dummyName/status?code={secrets.MasterKey}")) 208 | { 209 | using (HttpResponseMessage response = await client.SendAsync(request)) 210 | { 211 | return response.StatusCode == HttpStatusCode.NoContent || response.StatusCode == HttpStatusCode.OK || response.StatusCode == HttpStatusCode.NotFound; 212 | } 213 | } 214 | } 215 | 216 | private class UpdateContentLengthHandler : DelegatingHandler 217 | { 218 | public UpdateContentLengthHandler(HttpMessageHandler innerHandler) 219 | : base(innerHandler) 220 | { 221 | } 222 | 223 | protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) 224 | { 225 | // Force reading the content-length to ensure the header is populated. 226 | if (request.Content != null) 227 | { 228 | Trace.Write(request.Content.Headers.ContentLength); 229 | } 230 | 231 | return base.SendAsync(request, cancellationToken); 232 | } 233 | } 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /FunctionTestHelper/Helpers/TestHelpers.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using Microsoft.AspNetCore.Http.Internal; 3 | using Microsoft.Extensions.Primitives; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Diagnostics; 7 | using System.IO; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace FunctionTestHelper 12 | { 13 | public static class TestHelpers 14 | { 15 | 16 | public static Task Await(Func condition, int timeout = 30 * 1000, int pollingInterval = 2 * 1000, bool throwWhenDebugging = false, Func userMessageCallback = null) 17 | { 18 | return Await(() => Task.FromResult(condition()), timeout, pollingInterval, throwWhenDebugging, userMessageCallback); 19 | } 20 | public static async Task Await(Func> condition, int timeout = 60 * 1000, int pollingInterval = 2 * 1000, bool throwWhenDebugging = false, Func userMessageCallback = null) 21 | { 22 | DateTime start = DateTime.Now; 23 | while (!await condition()) 24 | { 25 | await Task.Delay(pollingInterval); 26 | 27 | bool shouldThrow = !Debugger.IsAttached || (Debugger.IsAttached && throwWhenDebugging); 28 | if (shouldThrow && (DateTime.Now - start).TotalMilliseconds > timeout) 29 | { 30 | string error = "Condition not reached within timeout."; 31 | if (userMessageCallback != null) 32 | { 33 | error += " " + userMessageCallback(); 34 | } 35 | throw new ApplicationException(error); 36 | } 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /FunctionTestHelper/Helpers/TestLogger.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Diagnostics; 7 | using System.Linq; 8 | using Microsoft.Extensions.Logging; 9 | 10 | namespace FunctionTestHelper 11 | { 12 | public class TestLogger : ILogger 13 | { 14 | private readonly Func _filter; 15 | private IList _logMessages = new List(); 16 | 17 | public TestLogger(string category, Func filter = null) 18 | { 19 | Category = category; 20 | _filter = filter; 21 | } 22 | 23 | public string Category { get; private set; } 24 | 25 | public IDisposable BeginScope(TState state) 26 | { 27 | return null; 28 | } 29 | 30 | public bool IsEnabled(LogLevel logLevel) 31 | { 32 | return _filter?.Invoke(Category, logLevel) ?? true; 33 | } 34 | 35 | public IList GetLogMessages() => _logMessages.ToList(); 36 | 37 | public void ClearLogMessages() => _logMessages.Clear(); 38 | 39 | public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) 40 | { 41 | if (!IsEnabled(logLevel)) 42 | { 43 | return; 44 | } 45 | 46 | var logMessage = new LogMessage 47 | { 48 | Level = logLevel, 49 | EventId = eventId, 50 | State = state as IEnumerable>, 51 | Exception = exception, 52 | FormattedMessage = formatter(state, exception), 53 | Category = Category, 54 | Timestamp = DateTime.UtcNow 55 | }; 56 | 57 | _logMessages.Add(logMessage); 58 | } 59 | } 60 | 61 | public class LogMessage 62 | { 63 | public LogLevel Level { get; set; } 64 | 65 | public EventId EventId { get; set; } 66 | 67 | public IEnumerable> State { get; set; } 68 | 69 | public Exception Exception { get; set; } 70 | 71 | public string FormattedMessage { get; set; } 72 | 73 | public string Category { get; set; } 74 | 75 | public DateTime Timestamp { get; set; } 76 | 77 | public override string ToString() => $"[{Timestamp.ToString("HH:mm:ss.fff")}] [{Category}] {FormattedMessage}"; 78 | } 79 | } -------------------------------------------------------------------------------- /FunctionTestHelper/Helpers/TestLoggerProvider.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using Microsoft.Extensions.Logging; 8 | 9 | namespace FunctionTestHelper 10 | { 11 | public class TestLoggerProvider : ILoggerProvider 12 | { 13 | private readonly Func _filter; 14 | 15 | public TestLoggerProvider(Func filter = null) 16 | { 17 | _filter = filter; 18 | } 19 | 20 | private Dictionary LoggerCache { get; } = new Dictionary(); 21 | 22 | public IEnumerable CreatedLoggers => LoggerCache.Values; 23 | 24 | public ILogger CreateLogger(string categoryName) 25 | { 26 | if (!LoggerCache.TryGetValue(categoryName, out TestLogger logger)) 27 | { 28 | logger = new TestLogger(categoryName, _filter); 29 | LoggerCache.Add(categoryName, logger); 30 | } 31 | 32 | return logger; 33 | } 34 | 35 | public IEnumerable GetAllLogMessages() => CreatedLoggers.SelectMany(l => l.GetLogMessages()).OrderBy(p => p.Timestamp); 36 | 37 | public void ClearAllLogMessages() 38 | { 39 | foreach (TestLogger logger in CreatedLoggers) 40 | { 41 | logger.ClearLogMessages(); 42 | } 43 | } 44 | 45 | public void Dispose() 46 | { 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /FunctionTestHelper/Helpers/TestLoggerProviderFactory.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using Microsoft.Azure.WebJobs.Script; 8 | using Microsoft.Azure.WebJobs.Script.Config; 9 | using Microsoft.Extensions.Logging; 10 | 11 | namespace FunctionTestHelper 12 | { 13 | public class TestLoggerProviderFactory : DefaultLoggerProviderFactory 14 | { 15 | private readonly TestLoggerProvider _loggerProvider; 16 | private readonly bool _includeDefaultLoggerProviders; 17 | 18 | public TestLoggerProviderFactory(TestLoggerProvider loggerProvider, bool includeDefaultLoggerProviders = true) 19 | { 20 | _loggerProvider = loggerProvider; 21 | _includeDefaultLoggerProviders = includeDefaultLoggerProviders; 22 | } 23 | 24 | public override IEnumerable CreateLoggerProviders(string hostInstanceId, ScriptHostConfiguration scriptConfig, ScriptSettingsManager settingsManager, Func fileLoggingEnabled, Func isPrimary) 25 | { 26 | return _includeDefaultLoggerProviders ? 27 | base.CreateLoggerProviders(hostInstanceId, scriptConfig, settingsManager, fileLoggingEnabled, isPrimary).Append(_loggerProvider) : 28 | new[] { _loggerProvider }; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /FunctionTestHelper/Helpers/TestSecretManager.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Threading.Tasks; 7 | using Microsoft.Azure.WebJobs.Host; 8 | using Microsoft.Azure.WebJobs.Script.WebHost; 9 | using Microsoft.Extensions.Logging; 10 | 11 | namespace FunctionTestHelper 12 | { 13 | public class TestSecretManager : ISecretManager 14 | { 15 | public virtual Task PurgeOldSecretsAsync(string rootScriptPath, ILogger logger) 16 | { 17 | throw new NotImplementedException(); 18 | } 19 | 20 | public virtual Task DeleteSecretAsync(string secretName, string keyScope, ScriptSecretsType secretsType) 21 | { 22 | return Task.FromResult(true); 23 | } 24 | 25 | public virtual Task> GetFunctionSecretsAsync(string functionName, bool merged) 26 | { 27 | return Task.FromResult>(new Dictionary 28 | { 29 | { "Key1", "Value1" }, 30 | { "Key2", "Value2" }, 31 | }); 32 | } 33 | 34 | public virtual Task GetHostSecretsAsync() 35 | { 36 | return Task.FromResult(new HostSecretsInfo 37 | { 38 | MasterKey = "1234", 39 | FunctionKeys = new Dictionary 40 | { 41 | { "HostKey1", "HostValue1" }, 42 | { "HostKey2", "HostValue2" }, 43 | }, 44 | SystemKeys = new Dictionary 45 | { 46 | { "SystemKey1", "HostValue1" }, 47 | { "SystemKey2", "HostValue2" }, 48 | } 49 | }); 50 | } 51 | 52 | public virtual Task AddOrUpdateFunctionSecretAsync(string secretName, string secret, string keyScope, ScriptSecretsType secretsType) 53 | { 54 | string resultSecret = secret ?? "generated"; 55 | return Task.FromResult(new KeyOperationResult(resultSecret, OperationResult.Created)); 56 | } 57 | 58 | public virtual Task SetMasterKeyAsync(string value) 59 | { 60 | throw new NotImplementedException(); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Jeff Hollan. All rights reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Azure Functions Test Fixture 2 | 3 | 🚧 This is a community driven proof of concept 🚧 4 | 5 | Test Azure Functions - not only with unit tests, but with integration tests executing the function as part of the host. The project contains 4 parts: 6 | 7 | |Project Folder|Description| 8 | |--|--| 9 | |FunctionApp|A Function App with triggers for HTTP, Blob, and Event Hubs| 10 | |FunctionApp.Tests|Unit tests for each of the Azure Functions| 11 | |FunctionApp.Tests.Integration|End-to-end integration tests that run the functions host and coordinate tests across the Azure Functions| 12 | |FunctionTestHelper|A helper project that includes methods for test running and generation| 13 | 14 | The test methods used are pulled from the [azure-functions-host](https://github.com/Azure/azure-functions-host/tree/dev/test/WebJobs.Script.Tests.Integration) integration tests, and this [Azure Sample](https://github.com/Azure-Samples/functions-unittesting-sample) 15 | 16 | Currently the project is only tested in Visual Studio 2017 for C# projects. However, you can follow some of the integration tests in the [azure-functions-host](https://github.com/Azure/azure-functions-host/tree/dev/test/WebJobs.Script.Tests.Integration/WebHostEndToEnd) project to see how other languages could be executed. 17 | 18 | ![screenshot](assets/screenshot.png) 19 | 20 | ## Setup 21 | 22 | **Pre-requisites**: Azure Functions tooling installed in Visual Studio 2017 23 | 24 | 1. Clone this repo: `git clone https://github.com/jeffhollan/functions-test-helper` 25 | 1. Open the solution in Visual Studio 2017 26 | 1. Add the [Azure App Services myGet feed](https://myget.org/gallery/azure-appservice) to NuGet: `https://www.myget.org/F/azure-appservice/api/v3/index.json` 27 | * This is used to pull the Azure Functions WebHost outside of the core tools 28 | 1. Build the solution 29 | 1. Add a folder for the webhost language workers 30 | There are some manual steps that are required. Specifically during execution the function WebHost will attempt to discover the language workers for Node and Java which are not included in the default worker directory "workers." Unfortunately, the webhost will look in a relative path for a folder called "workers." To work around this, open up the path where the NuGet package was restored. On Windows this is usually: 31 | 32 | `C:\Users\{username}\.nuget\packages\microsoft.azure.webjobs.script\2.0.0-{version}\lib\netstandard2.0` 33 | 34 | And create an empty folder called `workers`. If you want to run the language workers in the tests you will need to copy a valid workers folder from the core tools or other directory. 35 | 1. Provide the correct `local.settings.json` file in the `FunctionApp` project. 36 | * There is a sample provided. Just replace the placeholders with valid keys from an Azure Subscription. For the resources, the current code assumes: 37 | - The name of the Event Hub in the namespace is `test` 38 | - There is a container in the storage account called `test` 39 | 1. Run the `FunctionApp` project to make sure everything is installed correctly 40 | 1. Run all tests in the solution (using the Visual Studio test explorer) 41 | 42 | You can also view the outputs of any integration test. It will contain all logs from the host during the duration of the test. 43 | 44 | ## Updating the Azure Functions Host version 45 | 46 | Currently the project is set to the current public release of the Azure Functions runtime. As updates to the runtime occur, it would be your responsibility to also update the version of the WebHost NuGet package in the `FunctionTestHelper` project. You can see the number for each release and its status on [GitHub here](https://github.com/Azure/azure-functions-host/releases). 47 | 48 | > NOTE: At the time of publishing it this is an especially interesting time as the gap between the current Visual Studio tools and the current host runtime also transitions between .NET Core 2.0 and .NET Core 2.1, with some significant changes into dependency loading into the web host. Currently I have the .NET Core 2.0 webhost as part of the project and plan to revisit in the coming weeks to update to the current train of .NET Core 2.1 -------------------------------------------------------------------------------- /assets/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffhollan/functions-test-helper/7caa7af6c9e7ad3ce5199a405db01dd3b4c24ad1/assets/screenshot.png --------------------------------------------------------------------------------