Skip to content

Commit 818f7f9

Browse files
committed
Remove include file - only used once
1 parent b7fa5b0 commit 818f7f9

File tree

2 files changed

+237
-245
lines changed

2 files changed

+237
-245
lines changed

articles/azure-functions/functions-dotnet-class-library.md

Lines changed: 237 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -568,7 +568,243 @@ Don't call `TrackRequest` or `StartOperation<RequestTelemetry>` because you'll s
568568

569569
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.
570570

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**.
572808

573809
## Environment variables
574810

0 commit comments

Comments
 (0)