@@ -568,7 +568,243 @@ Don't call `TrackRequest` or `StartOperation<RequestTelemetry>` because you'll s
568
568
569
569
Don 't set `telemetryClient.Context.Operation.Id`. This global setting causes incorrect correlation when many functions are running simultaneously. Instead, create a new telemetry instance (`DependencyTelemetry`, `EventTelemetry`) and modify its `Context` property. Then pass in the telemetry instance to the corresponding `Track` method on `TelemetryClient` (`TrackDependency()`, `TrackEvent()`, `TrackMetric()`). This method ensures that the telemetry has the correct correlation details for the current function invocation.
570
570
571
- [! INCLUDE [functions - test - csharp .md ](.. / .. / includes / functions - test - csharp .md )]
571
+
572
+ ## Testing functions in C# in Visual Studio
573
+
574
+ The following example describes how to create a C # Function app in Visual Studio and run and tests with [xUnit ](https :// github.com/xunit/xunit).
575
+
576
+ ! [Testing Azure Functions with C # in Visual Studio ](.. / articles / azure - functions / media / functions - test - a - function / azure - functions - test - visual - studio - xunit .png )
577
+
578
+ ### Setup
579
+
580
+ To set up your environment , create a Function and test app . The following steps help you create the apps and functions required to support the tests :
581
+
582
+ 1 . [Create a new Functions app ](.. / articles / azure - functions / functions - get - started .md ) and name it ** Functions **
583
+ 2 . [Create an HTTP function from the template ](.. / articles / azure - functions / functions - get - started .md ) and name it ** MyHttpTrigger ** .
584
+ 3 . [Create a timer function from the template ](.. / articles / azure - functions / functions - create - scheduled - function .md ) and name it ** MyTimerTrigger ** .
585
+ 4 . [Create an xUnit Test app ](https :// xunit.net/docs/getting-started/netcore/cmdline) in the solution and name it **Functions.Tests**.
586
+ 5 . Use NuGet to add a reference from the test app to [Microsoft .AspNetCore .Mvc ](https :// www.nuget.org/packages/Microsoft.AspNetCore.Mvc/)
587
+ 6 . [Reference the * Functions * app ](/ visualstudio / ide / managing - references - in - a - project ) from * Functions .Tests * app .
588
+
589
+ ### Create test classes
590
+
591
+ Now that the projects are created , you can create the classes used to run the automated tests .
592
+
593
+ Each function takes an instance of [ILogger ](/ dotnet / api / microsoft .extensions .logging .ilogger ) to handle message logging . Some tests either don 't log messages or have no concern for how logging is implemented. Other tests need to evaluate messages logged to determine whether a test is passing.
594
+
595
+ You 'll create a new class named `ListLogger` which holds an internal list of messages to evaluate during a testing. To implement the required `ILogger` interface, the class needs a scope. The following class mocks a scope for the test cases to pass to the `ListLogger` class.
596
+
597
+ Create a new class in * Functions .Tests * project named ** NullScope .cs ** and enter the following code :
598
+
599
+ ```csharp
600
+ using System ;
601
+
602
+ namespace Functions .Tests
603
+ {
604
+ public class NullScope : IDisposable
605
+ {
606
+ public static NullScope Instance { get ; } = new NullScope ();
607
+
608
+ private NullScope () { }
609
+
610
+ public void Dispose () { }
611
+ }
612
+ }
613
+ ```
614
+
615
+ Next , create a new class in * Functions .Tests * project named ** ListLogger .cs ** and enter the following code :
616
+
617
+ ```csharp
618
+ using Microsoft .Extensions .Logging ;
619
+ using System ;
620
+ using System .Collections .Generic ;
621
+ using System .Text ;
622
+
623
+ namespace Functions .Tests
624
+ {
625
+ public class ListLogger : ILogger
626
+ {
627
+ public IList < string > Logs ;
628
+
629
+ public IDisposable BeginScope <TState >(TState state ) => NullScope .Instance ;
630
+
631
+ public bool IsEnabled (LogLevel logLevel ) => false ;
632
+
633
+ public ListLogger ()
634
+ {
635
+ this .Logs = new List <string >();
636
+ }
637
+
638
+ public void Log <TState >(LogLevel logLevel ,
639
+ EventId eventId ,
640
+ TState state ,
641
+ Exception exception ,
642
+ Func < TState , Exception , string > formatter )
643
+ {
644
+ string message = formatter (state , exception );
645
+ this .Logs .Add (message );
646
+ }
647
+ }
648
+ }
649
+ ```
650
+
651
+ The `ListLogger ` class implements the following members as contracted by the `ILogger ` interface :
652
+
653
+ - ** BeginScope ** : Scopes add context to your logging . In this case , the test just points to the static instance on the `NullScope ` class to allow the test to function .
654
+
655
+ - ** IsEnabled ** : A default value of `false ` is provided .
656
+
657
+ - **Log **: This method uses the provided `formatter ` function to format the message and then adds the resulting text to the `Logs ` collection .
658
+
659
+ The `Logs ` collection is an instance of `List <string >` and is initialized in the constructor .
660
+
661
+ Next , create a new file in * Functions .Tests * project named ** LoggerTypes .cs ** and enter the following code :
662
+
663
+ ```csharp
664
+ namespace Functions .Tests
665
+ {
666
+ public enum LoggerTypes
667
+ {
668
+ Null ,
669
+ List
670
+ }
671
+ }
672
+ ```
673
+
674
+ This enumeration specifies the type of logger used by the tests .
675
+
676
+ Now create a new class in * Functions .Tests * project named ** TestFactory .cs ** and enter the following code :
677
+
678
+ ```csharp
679
+ using Microsoft .AspNetCore .Http ;
680
+ using Microsoft .AspNetCore .Http .Internal ;
681
+ using Microsoft .Extensions .Logging ;
682
+ using Microsoft .Extensions .Logging .Abstractions ;
683
+ using Microsoft .Extensions .Primitives ;
684
+ using System .Collections .Generic ;
685
+
686
+ namespace Functions .Tests
687
+ {
688
+ public class TestFactory
689
+ {
690
+ public static IEnumerable < object []> Data ()
691
+ {
692
+ return new List <object []>
693
+ {
694
+ new object [] { " name" , " Bill" },
695
+ new object [] { " name" , " Paul" },
696
+ new object [] { " name" , " Steve" }
697
+
698
+ };
699
+ }
700
+
701
+ private static Dictionary < string , StringValues > CreateDictionary (string key , string value )
702
+ {
703
+ var qs = new Dictionary <string , StringValues >
704
+ {
705
+ { key , value }
706
+ };
707
+ return qs ;
708
+ }
709
+
710
+ public static HttpRequest CreateHttpRequest (string queryStringKey , string queryStringValue )
711
+ {
712
+ var context = new DefaultHttpContext ();
713
+ var request = context .Request ;
714
+ request .Query = new QueryCollection (CreateDictionary (queryStringKey , queryStringValue ));
715
+ return request ;
716
+ }
717
+
718
+ public static ILogger CreateLogger (LoggerTypes type = LoggerTypes .Null )
719
+ {
720
+ ILogger logger ;
721
+
722
+ if (type == LoggerTypes .List )
723
+ {
724
+ logger = new ListLogger ();
725
+ }
726
+ else
727
+ {
728
+ logger = NullLoggerFactory .Instance .CreateLogger (" Null Logger" );
729
+ }
730
+
731
+ return logger ;
732
+ }
733
+ }
734
+ }
735
+ ```
736
+
737
+ The `TestFactory ` class implements the following members :
738
+
739
+ - ** Data ** : This property returns an [IEnumerable ](/ dotnet / api / system .collections .ienumerable ) collection of sample data . The key value pairs represent values that are passed into a query string .
740
+
741
+ - ** CreateDictionary ** : This method accepts a key / value pair as arguments and returns a new `Dictionary ` used to create `QueryCollection ` to represent query string values .
742
+
743
+ - ** CreateHttpRequest ** : This method creates an HTTP request initialized with the given query string parameters .
744
+
745
+ - ** CreateLogger ** : Based on the logger type , this method returns a logger class used for testing . The `ListLogger ` keeps track of logged messages available for evaluation in tests .
746
+
747
+ Finally , create a new class in * Functions .Tests * project named ** FunctionsTests .cs ** and enter the following code :
748
+
749
+ ```csharp
750
+ using Microsoft .AspNetCore .Mvc ;
751
+ using Microsoft .Extensions .Logging ;
752
+ using Xunit ;
753
+
754
+ namespace Functions .Tests
755
+ {
756
+ public class FunctionsTests
757
+ {
758
+ private readonly ILogger logger = TestFactory .CreateLogger ();
759
+
760
+ [Fact ]
761
+ public async void Http_trigger_should_return_known_string ()
762
+ {
763
+ var request = TestFactory .CreateHttpRequest (" name" , " Bill" );
764
+ var response = (OkObjectResult )await MyHttpTrigger .Run (request , logger );
765
+ Assert .Equal (" Hello, Bill. This HTTP triggered function executed successfully." , response .Value );
766
+ }
767
+
768
+ [Theory ]
769
+ [MemberData (nameof (TestFactory .Data ), MemberType = typeof (TestFactory ))]
770
+ public async void Http_trigger_should_return_known_string_from_member_data (string queryStringKey , string queryStringValue )
771
+ {
772
+ var request = TestFactory .CreateHttpRequest (queryStringKey , queryStringValue );
773
+ var response = (OkObjectResult )await MyHttpTrigger .Run (request , logger );
774
+ Assert .Equal ($" Hello, {queryStringValue }. This HTTP triggered function executed successfully." , response .Value );
775
+ }
776
+
777
+ [Fact ]
778
+ public void Timer_should_log_message ()
779
+ {
780
+ var logger = (ListLogger )TestFactory .CreateLogger (LoggerTypes .List );
781
+ MyTimerTrigger .Run (null , logger );
782
+ var msg = logger .Logs [0 ];
783
+ Assert .Contains (" C# Timer trigger function executed at" , msg );
784
+ }
785
+ }
786
+ }
787
+ ```
788
+
789
+ The members implemented in this class are :
790
+
791
+ - ** Http_trigger_should_return_known_string ** : This test creates a request with the query string values of `name = Bill ` to an HTTP function and checks that the expected response is returned .
792
+
793
+ - **Http_trigger_should_return_string_from_member_data **: This test uses xUnit attributes to provide sample data to the HTTP function .
794
+
795
+ - ** Timer_should_log_message ** : This test creates an instance of `ListLogger ` and passes it to a timer functions . Once the function is run , then the log is checked to ensure the expected message is present .
796
+
797
+ If you want to access application settings in your tests , you can [inject ](.. / articles / azure - functions / functions - dotnet - dependency - injection .md ) an `IConfiguration ` instance with mocked environment variable values into your function .
798
+
799
+ ### Run tests
800
+
801
+ To run the tests , navigate to the ** Test Explorer ** and click ** Run all ** .
802
+
803
+ ! [Testing Azure Functions with C # in Visual Studio ](.. / articles / azure - functions / media / functions - test - a - function / azure - functions - test - visual - studio - xunit .png )
804
+
805
+ ### Debug tests
806
+
807
+ To debug the tests , set a breakpoint on a test , navigate to the ** Test Explorer ** and click ** Run > Debug Last Run ** .
572
808
573
809
## Environment variables
574
810
0 commit comments