Running tests for different environment and different versions C#-MSTest - c#

I am creating an automated test framework to test an API using .NetCore, RestSharp and MsTest. I am looking for ideas to run my tests for 4 different environments (2 different countries, test and live environment each). I will access test data through appsettings.json file (e.g appsettings.eutest.json, appsetings.detest.json, etc).
I could use something like [DynamicData()] to pass each test an argument with the key to access each individual .json file, but there is a subset of tests, compatibility tests, where I need to run also again different versions of the API, this is needed to ensure backward compatibility.
[DynamicData("NameOfPropertyWithEnvironments", typeof(BaseClass))]
[TestMethod]
public void RegularTest(string envToTest)
{
//-- Logic to access data on the .json file
}
[DynamicData("NameOfPropertyWithVersions", typeof(BaseClass))]
[TestMethod]
public void CompatibilityTest(int versionBackToTest)
{
}
So far, I have been using also [DynamicData()] to pass the versions to be tested as an argument of those particular tests, but I am lost as to how to combine different environments and different versions.
Thanks in advance!

To run these tests locally, you can create a local.settings.json file in your test project with APIEndpoint and APIVersion settings.
Next, implement a configuration manager that can firstly determine whether you are running the tests locally. If so, read the settings from local.settings.json. If not, read from environment variables. Here is an example of what I use:
namespace SomeIntegrationTests.configuration
{
class Configuration
{
public string API_ENDPOINT { get; set; }
public string API_VERSION { get; set; }
}
class ConfigurationManager
{
private const string LocalSettingsFile = "local.settings.json";
private static Configuration _configuration;
public static Configuration Get()
{
if (_configuration == null)
{
if (IsLocalRun())
{
var localSettings = ReadFile(LocalSettingsFile);
_configuration = JsonConvert.DeserializeObject<Configuration>(localSettings);
}
else
{
_configuration = GetFromEnvironmentVariables();
Console.WriteLine($"Retrieved configuration from environment variables.");
}
}
return _configuration;
}
private static Configuration GetFromEnvironmentVariables()
{
var environment = GetEnvironmentVariable("CURRENT_ENVIRONMENT");
Console.WriteLine($"Current environment is {environment}");
return new Configuration
{
API_ENDPOINT = GetEnvironmentVariable($"API_ENDPOINT.{environment}"),
API_VERSION = GetEnvironmentVariable($"API_VERSION.{environment}")
};
}
private static string GetEnvironmentVariable(string key)
{
return Environment.GetEnvironmentVariable(key);
}
private static bool IsLocalRun()
{
var environment = GetEnvironmentVariable("CURRENT_ENVIRONMENT");
return environment == "local";
}
private string ReadFile(string file)
{
var projectFolderLocation = // depends on the type of implementation get location of where the local.settings.json is located;
var filePath = Path.Combine(projectFolderLocation, "configuration", file);
return File.ReadAllText(filePath);
}
}
}
On your local pc, add an environment variable for "CURRENT_ENVIRONMENT" with value "local" and add local.appsettings.json in your project. In your pipeline add a step to set environment variable values accordingly. When you run the tests during pipeline, settings will be used from the environment variables settings.
Then from your test, you can load the Configuration via ConfigurationManager implementation and then call API_ENDPOINT and API_VERSION.
class SomethingTests
{
private readonly string APIEndpoint;
private readonly string APIVersion;
public SomethingTests()
{
var configuration = ConfigurationManager.Get();
APIEndpoint = configuration.API_ENDPOINT;
APIVersion = configuration.API_VERSION;
}
[TestMethod]
public void SomethingTests()
{
// use APIEndpoint & APIVersion
}
}
As for running tests against production environments, this doesn't seem like a good idea. You don't want to insert test data on production. Implement automated deployment steps to ensure you tested your API code on test environments and deploy to production after that.
Does this help?

Related

Context Injection in Specflow failing in c# when parallel test are executed in xunit, but works in sequential run

I created a xunit project and added specflow with Gherkin and selenium web drivers to the project. I've following StepDefinition file,
[Binding]
public class MyPageStepDefinition
{
private readonly UserContext _userContext;
private readonly ConfigurationDriver cd;
public MyPageStepDefinition(UserContext userContext)
{
_userContext = userContext;
cd = new ConfigurationDriver();
}
// Many Given, when and Then
}
I've UserContext file as follows,
public class UserContext
{
public string email { get; set; }
}
I'm using IObjectContainer to get help with ContextInjection in specflow as follows,
[Binding]
public class BrowserDriver
{
private readonly IObjectContainer _objectContainer;
// BrowserType is enum
public Dictionary<BrowserType, IWebDriver> _drivers;
public BrowserDriver(IObjectContainer objectContainer) {
_objectContainer = objectContainer;
_drivers = new Dictionary<BrowserType, IWebDriver>();
}
[BeforeScenario]
public void BeforeScenario()
{
_objectContainer.RegisterInstanceAs(_drivers);
}
}
My browser initialization code,
public IWebDriver InitBrowser(BrowserType browser)
{
IWebDriver driver = null;
switch (browser)
{
case BrowserType.Chrome:
ChromeOptions chromeOptions = new ChromeOptions();
if (!Debugger.IsAttached)
{
chromeOptions.AddArgument("headless");
chromeOptions.AddArguments("disable-gpu");
chromeOptions.AddArguments("window-size=1900,1280");
chromeOptions.AddArguments("--no-sandbox");
chromeOptions.AddArguments("--ignore-certificate-errors");
chromeOptions.AddArguments("--disable-dev-shm-usage");
}
driver = new ChromeDriver(chromeOptions);
break;
}
driver.Manage().Window.Maximize();
//driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(10);
return driver;
}
I call above method as follows,
SupportedBrowserList().ForEach(b => _drivers.Add(b, _testContext.InitBrowser(b)));
public List<BrowserType> SupportedBrowserList()
{
return new List<BrowserType>
{
BrowserType.Chrome,
};
}
and I use _drivers as follows,
GetCurrentDriverList(_drivers).ForEach(d =>
{
// Do something on webpage, like d.FindElement(..);
});
public List<IWebDriver> GetCurrentDriverList(Dictionary<BrowserType, IWebDriver> drivers)
{
return new List<IWebDriver>(drivers.Values);
}
I've 2 features files to handle 2 flows. Most of the steps are common so they both call MyPageStepDefinition constructor. When I run the feature files parallelly (default behavior of xunit) then one or the other test case fail, saying the email is not matching. I was able to debug and understand, that the email of one test is going in another. Looks like there is some race condition in Context Injection in specflow in parallel execution.
To validate my hypothesis, I made the test run sequentially using xunit.runner.json file with following configuration,
{
"parallelizeTestCollections": false
}
and the code worked flawlessly. Now, if I run again in parallel it fails. It confirmed my hypothesis that specflow+xunit in parallel run is causing some issues. I'm pretty sure someone must have faced similar issue and fixed it, but I can't seem to figure it out. Can someone please help me in this? And How can I fix this?
Note: I've made the file simple, the UserContext has more data fields. None of the fields are static in UserContext.

In a stand-alone .NET Core DLL, how do I get a value from the appsettings.json config file?

EDIT: Passing down the configuration from an ASP.NET controller is not going to work if it is running in a console application.
Background:
- The DLL will be shipped with different applications, some console, some ASP.NET MVC and some WebAPI
- I'm building a .NET Core C# processing DLL which needs several configuration entries from appsettings.json.
- I'd like to make the DLL C# methods all static if possible for simplicity.
- An example configuration entry would be "SxProcFull" with values of true or false. There are about 10 configuration entries needed for the DLL
- The DLL is part of a much larger code base and is called multiple levels down from a web controller method or console app's main method
How can I get the configuration entry from inside the DLL?
Most of the examples on the web are for ASP.NET and oversimplify by putting the config entry code in the controller method with the configuration service passed into the controller method.
The .NET Core docs are long on what type of providers exist yet short on real-world examples.
Related links:
Understanding .net Core Dependency Injection in a console app
https://espressocoder.com/2018/12/03/build-a-console-app-in-net-core-like-a-pro/
https://blog.bitscry.com/2017/05/30/appsettings-json-in-net-core-console-app/
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/?view=aspnetcore-3.0&tabs=windows
And many more...
Edit: Moving the multi-app type item to top of background list.
ANSWER:
using System;
using System.IO;
using System.Reflection;
using Microsoft.Extensions.Configuration;
namespace AspNetCoreTestProject2
{
public static class MyConfiguration
{
private static IConfigurationRoot GetConfigRoot()
{
var assemblyLoc = Assembly.GetExecutingAssembly().Location;
var directoryPath = Path.GetDirectoryName(assemblyLoc);
var configFilePath = Path.Combine(directoryPath, "appsettings.json");
if (File.Exists(configFilePath) == false)
{
throw new InvalidOperationException("Config file not found");
}
IConfigurationBuilder builder = new ConfigurationBuilder();
builder.AddJsonFile(configFilePath);
var configRoot = builder.Build();
return configRoot;
}
public static string ConfigGetConnectionStringByName(string connnectionStringName)
{
if (string.IsNullOrWhiteSpace(connnectionStringName))
{
throw new ArgumentException(nameof(connnectionStringName));
}
var root = GetConfigRoot();
var ret = root.GetConnectionString(connnectionStringName);
if (string.IsNullOrWhiteSpace(ret))
{
throw new InvalidOperationException("Config value cannot be empty");
}
return ret;
}
public static string ConfigEntryGet(string configEntryName)
{
if (string.IsNullOrWhiteSpace(configEntryName))
{
throw new ArgumentException(nameof(configEntryName));
}
var root = GetConfigRoot();
var ret = root[configEntryName];
if (string.IsNullOrWhiteSpace(ret))
{
throw new InvalidOperationException("Config value cannot be empty");
}
return ret;
}
}
}
private readonly IConfiguration _config;
public YourController(IConfiguration config)
{
_config = config;
}
public IActionResult Testing()
{
string getKey = _config["Token:Key"]
}
If you have this sample json
"Token": {
"Key": "ktF2YqrBlhfdg444"
}
I don't know why you copy the JSON in the folder with the Dlls and use this JSON. On Visual Studio you can select the JSON and set the property "copy into output path". If you do it like this you can use IConfiguration.
The class you get from the Initialization-Method in your Controller Class.
Eg.:
public ApiController(IConfiguration configuration)
{
Configuration = configuration;
}
private IConfiguration Configuration;
On other C# Appliactionyou can use this Nuget
So read the JSON File with the StreamReader.
StreamReader sw = new StreamReader("myjason.json");
string json = sw.ReadToEnd();
Then use the JObject
JObject obj = JObject.Parse(json);
string mainpath = (string)obj["paths"]["mainpath"];
Hope it helps.
BierDav

Design pattern for accessing static data

I have a scenario where I have a set of credentials for each environment
e.g. for dev env username1/pwd1, for qa env username2/pwd2, for staging username3/pwd3 and so on.
Now I want to create a class which will return me a set of credentials based on the env I feed to it.
All the data has to go within code (as per my brilliant boss, no xml files and all), what design pattern I could use so that the code will be elegant and data can be made extensible in future?
Personally, I am used to create a channel attribute:
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false)]
public sealed class AssemblyChannelAttribute : Attribute
{
public ChannelType Type { get; private set; }
public AssemblyChannelAttribute(ChannelType type)
{
this.Type = type;
}
}
public enum ChannelType
{
Dev,
Beta,
PreProd,
Prod
}
This attribute is set on the Assembly:
#if DEBUG
// In release mode, this attribute is set by the MSBuild script
[assembly: AssemblyChannel(ChannelType.Dev)]
#else
As the comment said, the value of the attribute is set on compile time by my MSBuild script (too tied to my project to show you this part).
Once you have setup all of this, you can create a simple singleton like this:
public class Credentials
{
private static readonly Lazy<Credentials> instanceHolder =
new Lazy<Credentials>(() => new Credentials());
public IReadOnlyDictionary<string, string> Passwords { get; private set; }
public Credentials Instance { get { return instanceHolder.Value; } }
private Credentials()
{
var channel = typeof(Credentials).Assembly
.GetCustomAttributes<AssemblyChannelAttribute>()
.ElementAt(0)
.Type;
switch (channel)
{
case ChannelType.Dev:
this.Passwords = new ReadOnlyDictionary<string, string>(new Dictionary<string, string>
{
["User1"] = "Pwd1",
["User2"] = "Pwd2",
// etc
});
break;
case ChannelType.Beta:
// etc
break;
case ChannelType.PreProd:
// etc
break;
case ChannelType.Prod:
// etc
break;
}
}
}
Then you can access your credentials like this:
var password = Credentials.Instance.Passwords["User1"];
If you use .Net core, you could use the configuration techniques.
They are very powerful and work in asp .net as well as console programs
They are very configurable and composable (pass in config via cmd and json for example)
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration
What you're after is the Multiton design pattern.

I would like to know how to load config so it would work both in test project and during application runtime

I would like to encapsulate AppConfig therefore I prepared class like this:
static AppConfig()
{
var exePath = Assembly.GetExecutingAssembly().Location;
_config = ConfigurationManager.OpenExeConfiguration(exePath);
}
I did that in order to have Save possibility and can add new settings like this:
public static void SetSettings(string key, object value)
{
if (_config.AppSettings.Settings.AllKeys.Contains(key))
{
_config.AppSettings.Settings[key].Value = value.ToString();
}
else
{
_config.AppSettings.Settings.Add(key, value.ToString());
}
}
public static string GetSettings(string key, string defaultValue = "")
{
if (_config.AppSettings.Settings.AllKeys.Contains(key))
{
return _config.AppSettings.Settings[key].Value;
}
return defaultValue;
}
public static void Save()
{
_config.Save(ConfigurationSaveMode.Modified);
}
Unfortunately using such AppCofing in Tests does not loads it.
would like to know how to load config so it would work both in test project and during application runtime.
The unit test should not be reading from external config files. The values being read out should be dependencies to whatever consumes them, so your test can just provide values you require through the constructor, method, property etc.
If you want to unit test getting and setting of values in the config file, then that is probably not worth testing, as that is framework code.

Using Webconfig settings in Unit Tests

I have some appsetting related to SendGrid Service like username, password and some resetpasswordemail settings. Now i wanted to use them in my unit test. My ResetPasswordSetting class looks like this.
public class ResetPasswordEmailSetting : IResetPasswordEmailSetting
{
public string ResetPasswordEmailUrl { get; set; }
public List<string> AddToEmails { get; set; }
public string FromEmail
{
get { return ConfigurationManager.AppSettings.Get("ResetPassword_FromEmail"); }
}
public string Subject
{
get { return ConfigurationManager.AppSettings.Get("ResetPassword_Subject"); }
}
public string SenderDisplayName
{
get { return ConfigurationManager.AppSettings.Get("ResetPassword_SenderDisplayName"); }
}
}
When i run my unit test using this class then the fields that get their value from Webconfig appsetting part are coming null.
My Unit Test looks like this.
[TestMethod]
[TestCategory("SendGrid-Service")]
public async Task email_recieved_when_correct_email_provided()
{
var sendgrid = new SendGridService();
var resetpasswordsetting = new ResetPasswordEmailSetting
{
AddToEmails = new List<string> { "fluffyduk#gmail.com" },
ResetPasswordEmailUrl = "www.google.com",
};
// when i debug then resetpasswordsetting fields are coming null other then above.
var result = await sendgrid.SendResetPasswordEmail(resetpasswordsetting, new
SendGridSettings());
result.Should().BeTrue();
}
so any idea how to get these settings in unit test.
The first thing that comes to my mind is that in this test you are NOT interested in the settings, the subject of your test is SendResetPasswordEmail method from the SendGridService class. Remember you are implementing UNIT tests, not integration tests or something like that. While unit testing you should strive to isolate any dependencies and config settings is one dependency your class doesn't really need to test a specific business logic. For this purpose you should create a mock object instead because, again, config settings are NOT needed to test a piece of functionality.
However, if you still insist in using real config files there's nothing stopping you from adding an app.config file to your unit test project and specifying all the appSettings your tests require

Categories