I'm using the dotnet Testcontainers library to spin up a SQL Server database in Docker for integration testing. I'm using the MSTest framework.
My idea is to run a TestInitialize where the container will start, populate the database and run assertions in the test method, and then run a TestCleanup at the end which will stop the container and dispose of it.
However, the container starts up in Docker and the test hangs (I guess it never ends up running for some reason). Also I'm not 100% sure how to populate the database (I couldn't find any commands for initialising and SQL scripts).
Here is the code:
[TestClass]
public class WithFixtureData
{
private static readonly TestcontainersContainer _dbContainer =
new TestcontainersBuilder<TestcontainersContainer>()
.WithImage("mcr.microsoft.com/mssql/server")
.WithEnvironment("Database", "Master")
.WithEnvironment("User Id", "SA")
.WithEnvironment("Password", "YourSTRONG!Passw0rd")
.WithCleanUp(true)
.Build();
[TestInitialize]
public async Task StartContainer()
{
await _dbContainer.StartAsync();
///container starts
}
[TestMethod]
public async Task ShouldBringCorrectFixturesBack()
{
///populate db and run assertions. This code never seems to run
}
[TestCleanup]
public async Task DisposeContainer()
{
await _dbContainer.StopAsync();
///this part of the code never seems to be executed either
}
}
I am wondering how the example above even runs, the configuration looks wrong. I attached a working example below, but first, lets take a look into the issues:
The environment variables do not exist. The mcr.microsoft.com/mssql/server image has the following environment variables (see section Environment Variables) to configure SQL Server on Linux containers. The section How to use this Image might also help. To run the container at least these configurations are necessary:
.WithEnvironment("ACCEPT_EULA", "Y")
.WithEnvironment("MSSQL_SA_PASSWORD", "yourStrong(!)Password")
The database port is not exposed, you cannot connect to the database. To expose and bind a container port to a random public host port use .WithPortBinding(1433, true). Here is another example that shows how to expose a container port.
Your configuration does not use a wait strategy to indicate readiness of the service running inside the container. A wait strategy for SQL Server could be something like:
.WithWaitStrategy(Wait.ForUnixContainer().UntilCommandIsCompleted("/opt/mssql-tools/bin/sqlcmd", "-S", "localhost,1433", "-U", "sa", "-P", "yourStrong(!)Password"))
A working configuration will look something like:
[TestClass]
public sealed class SO
{
private const string Database = "master";
private const string Username = "sa";
private const string Password = "yourStrong(!)Password";
private const ushort MssqlContainerPort = 1433;
private readonly TestcontainersContainer _dbContainer =
new TestcontainersBuilder<TestcontainersContainer>()
.WithImage("mcr.microsoft.com/mssql/server:2022-latest")
.WithPortBinding(MssqlContainerPort, true)
.WithEnvironment("ACCEPT_EULA", "Y")
.WithEnvironment("MSSQL_SA_PASSWORD", Password)
.WithWaitStrategy(Wait.ForUnixContainer().UntilCommandIsCompleted("/opt/mssql-tools/bin/sqlcmd", "-S", $"localhost,{MssqlContainerPort}", "-U", Username, "-P", Password))
.Build();
[TestInitialize]
public Task StartContainer()
{
return _dbContainer.StartAsync();
}
[TestCleanup]
public Task DisposeContainer()
{
return _dbContainer.StopAsync();
}
[TestMethod]
public Task Question_74323116()
{
var connectionString = $"Server={_dbContainer.Hostname},{_dbContainer.GetMappedPublicPort(MssqlContainerPort)};Database={Database};User Id={Username};Password={Password};";
using (var sqlConnection = new SqlConnection(connectionString))
{
try
{
sqlConnection.Open();
}
catch
{
Assert.Fail("Could not establish database connection.");
}
}
return Task.CompletedTask;
}
}
Related
I have this in the ConfigureServices method:
services.AddHttpClient("ClientUsingCredentials")
.ConfigurePrimaryHttpMessageHandler(() =>
{
var credentials = new NetworkCredential("someUsername", "somePassword");
return new HttpClientHandler()
{
// UseDefaultCredentials = true,
Credentials = credentials
};
});
Now, my service looks like this:
public class WebAppService : IWebAppService
{
private readonly ILogger<WebAppService> _logger;
private readonly IHttpClientFactory _httpClientFactory;
public WebAppService(ILogger<WebAppService> logger, IHttpClientFactory httpClientFactory)
{
_logger = logger;
_httpClientFactory = httpClientFactory;
}
public async Task<WebAppReport> CheckWebSiteWithCredentialsAsync(string someUrlThatNeedsCredentials)
{
WebAppReport report = new();
try
{
using (var client = _httpClientFactory.CreateClient("ClientUsingCredentials"))
{
var result = await client.GetAsync(someUrlThatNeedsCredentials);
if (result.IsSuccessStatusCode)
{
// Do something with the result
report.IsCheckSuccessful = true;
}
else
{
// Set report fields accordingly
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, $"Something went wrong while checking the website: '{someUrlThatNeedsCredentials}'.");
// Set report fields accordingly
}
return report;
}
}
The WebAppReport class:
public class WebAppReport
{
public bool IsCheckSuccessful { get; set; }
//Some other fields here
}
Is there a way to write unit test for this service method that also checks that it passes correct credentials while sending out requests?
Edit: I see #Nkosi's answer saying that Unit testing is not possible in this case. So, can you please show an example for doing integration test on it?
Should I be creating the service in my test method using new ServiceCollection(), add the service in the same way I did in ConfigureServices method, set correct credentials and so on? Would love to see your example on it.
Thank you!
Is there a way to write unit test for this method that also checks that it passes correct credentials while sending out requests?
Short answer: No. Not in a unit test.
That is because the ConfigurePrimaryHttpMessageHandler is a framework cross-cutting concern that is only applied by the actual HttpClientFactory at run time when creating the actual client that will be used to make the request.
You are basically trying to test that the HttpClientFactory implementation will behave as designed. Microsoft would have tested that before releasing it for use.
For your specific scenario, something like that would ideally need to be done in an integration test.
For testing your WebAppService you should focus on the logic local to the subject under test.
I've writing various tests for my app and now I got to a problem that I'm unable to solve.
The test I'm writing is a simple command that executes and action that modifies a database and then a query to validate that the values are correct.
In order to connect to the database my BaseContext gets the connection string from an interface:
public BaseContext(DbContextOptions options, IConnectionStringProvider connectionStringProvider)
: base(options)
{
_connectionStringProvider = connectionStringProvider;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
var connString =_connectionStringProvider.GetConnectionString();
optionsBuilder.UseOracle(connString);
}
}
My connection string provider interface looks like this:
public interface IConnectionStringProvider
{
string GetConnectionString();
Dictionary<string, string> GetConnectionSession();
}
And the implementation looks like this:
public class HttpConnectionStringProvider : IConnectionStringProvider
{
public HttpConnectionStringProvider(IConfiguration configuration, IHttpContextAccessor httpContextAccessor)
{
_configuration = configuration ?? throw new NullReferenceException(nameof(configuration));
_httpContext = httpContextAccessor.HttpContext ?? throw new NullReferenceException("HttpContext");
}
public string GetConnectionString()
{
// Do something with http context and configuration file and return connection string
}
}
All this interfaces are registered using autofac.
Then when executing the following test:
[Test]
public async Task AddProductTest()
{
string connectionString = "fixed_connection_string";
var mock = new MockRepository(MockBehavior.Default);
var mockConnectionStringProvider = new Mock<IConnectionStringProvider>();
mockConnectionStringProvider.Setup(x => x.GetConnectionString())
.Returns(connectionString);
await ProductModule.ExecuteCommandAsync(new AddProductCommand(1, "nameProduct"));
var products = await ProdcutModule.ExecuteQueryAsync(new GetProducts(1));
// Assert
Assert.That(products .Status, Is.EqualTo(ResultQueryStatus.Ok));
Assert.That(products .Value, Is.Not.Empty);
Assert.That(products .Value.Any(x => x.Name== "nameProduct"));
}
The IConfiguration and IHttpContextAccessor are mocked using NSubstitute library. But even if I deleted them just to test if IConnectionStringProvider returned the value expected in setup it didn't work.
When running the test in debug I see that steps into the method GetConnectionString() when it should be mocked. I don't know what I'm doing wrong I suppose there is something that I don't understand about testing.
I am curious what you are attempting to do:
Mocking out the connection string for creating someone like a DB connection string or similar, doesn't make a lot of sense. It is part of the base functionality of the DI bits for C# service handling in Setup.
if you want to mess with this, of course you can do it, but the purpose escapes me a bit.
However, if you simply wish to test ProductModule as something you have written, then it makes more sense.
Then when you make this statement:
ProductModule productModule = new ProductModule( - parameters - );
your parameters likely requires a IConnectionStringProvider.
There you need to use your mockConnectionStringProvider.Object
That will allow you to insert your mocked object, as the object used in your ProductModule constructor.
I have two tests of an ASP.NET core webapp within a single test class. The class uses an IClassFixture<WebApplicationFactory<Startup>>.
The first test is a happy path test: when a message arrives on a Kafka topic, some file appears in a particular output directory.
The second test is to verify that if something goes wrong with outputting said file, the webapp should then be in an unhealthy status.
For brevity, I'm omitting the code that publishes to Kafka and that generates the output folder the application saves files to - neither of these are relevant.
[CollectionDefinition("e2e", DisableParallelization = true)]
[Trait("Category", "IntegrationTest")]
public class EndToEndTest : IClassFixture<WebApplicationFactory<Startup>>, IClassFixture<KafkaFixture>
{
private readonly WebApplicationFactory<Startup> _factory;
public EndToEndTest(WebApplicationFactory<Startup> factory)
{
_factory = factory;
KafkaUtils.Publish(SomeMessage()).GetAwaiter().GetResult();
}
[Fact]
public void WhenXReceived_ThenFileIsOutput()
{
var options = OutputFolder();
Directory.CreateDirectory(options.Path);
RunService(services => services.AddSingleton(Options.Create(options))).CreateClient();
Thread.Sleep(10_000);
var outputDirectory = Directory.GetFiles(options.Path);
Assert.Single(outputDirectory);
var file = outputDirectory.Single();
Assert.NotEmpty(File.ReadAllLinesAsync(file).GetAwaiter().GetResult());
Directory.Delete(options.Path, true);
}
[Fact]
public void WhenFileTransferFails_ThenAppShouldBeUnhealthy()
{
var options = OutputFolder();
// only try once, so we don't have to wait
var retry = new RetryPolicyConfiguration {OnErrorRetryCount = 1};
var factory = RunService(services =>
{
services.AddSingleton(Options.Create(options));
services.AddSingleton(Options.Create(retry));
services.RemoveAll<IFileWriter>();
services.AddScoped<IFileWriter, ThrowingFileWriter>();
});
var client = factory.CreateClient();
Thread.Sleep(10_000);
Assert.False(Directory.Exists(options.Path));
var response = client.GetAsync("/health/ready").GetAwaiter().GetResult();
var stream = response.Content.ReadAsStreamAsync().GetAwaiter().GetResult();
using var reader = new StreamReader(stream);
var body = reader.ReadToEndAsync().GetAwaiter().GetResult();
var health = JsonConvert.DeserializeObject<HealthCheckResult>(body);
Assert.NotEqual(HealthStatus.Healthy, health.Status);
}
protected WebApplicationFactory<Startup> RunService(Action<IServiceCollection> serviceConfig)
{
return _factory
.WithWebHostBuilder(builder => builder
.ConfigureTestServices(services =>
{
serviceConfig(services);
services.AddSubscribeBus(new ConfigurationBuilder()
.AddJsonFile(ConfigMapFileProvider.FromRelativePath("config"),
"appsettings.e2e.json", true, true)
.Build());
}));
}
}
public class ThrowingFileWriter : IFileWriter
{
public void Write(string fileName, envelope envelope)
{
throw new Exception("foo");
}
public void Delete(string fileName)
{
}
}
If I run WhenFileTransferFails_ThenAppShouldBeUnhealthy() (unhealthyTest from here on, for brevity) on its own, then the test passes. But if I run the entire test class, then WhenXReceived_ThenFileIsOutput() (healthyTest, for brevity) runs before it, and for some reason unhealthyTest seems to use the service context that was created for healthyTest.
I've tried my best to work out what's going on, but it doesn't make sense to me. When I debug through it, the order of events is roughly as follows:
healthyTest starts
Startup invoked for healthytest
Constructor of service that uses IFileWriter is invoked with real instance of service
healthyTest completes
unhealthyTest starts
Constructor of service that uses IFileWriter is invoked with real instance of service (???)
Startup invoked for unhealthyTest
serviceConfig action invoked for unhealthyTest: all implementations of IFileWriter replaced with ThrowingFileWriter - but this is too late, it's already been constructed with the real instance in step 6
Consequently, the real service is used instead of the throwing service, and the exception handler that sets the app status to Unhealthy is never invoked.
Originally this was all running asynchronously, but I thought that perhaps the async nature of the tests meant that the two were conflicting with the webapp created by the factory - hence all the GetAwaiter().GetResult().
What am I doing wrong in my setup of the webapp for the test?
Note: it's unfortunately absolutely not an option to move these tests into their own test classes.
I'm testing a WCF service methods using specflow and nunit; my scenarios look like the following:
Feature: GetAccount
Testing API method 'get account'
Background:
Given Server is running
Scenario: Succesful Get
Given An Existing Account
When I call the GetAccount API method With password = "123"
Then the result should be Success
I'm not sure on how to implement the background step;
The server can be run as console / windows service using Topshelf-
private static void Main()
{
Host host = HostFactory.New(config =>
{
config.Service<ServiceInitializer>(service =>
{
service.ConstructUsing(s => new ServiceInitializer());
service.WhenStarted((s, control) => s.Start(control));
service.WhenStopped((s, control) => s.Stop(control));
});
config.RunAsPrompt();
});
host.Run();
}
public class ServiceInitializer : ServiceControl
{
private readonly ILog m_log;
public ServiceInitializer()
{
log4net.Config.XmlConfigurator.Configure();
m_log = LogManager.GetLogger("Server");
}
public bool Start(HostControl hostControl)
{
try
{
var host = new IoCServiceHost(typeof(MyService));
host.Open();
m_log.Info("Server is now open.");
return true;
}
catch (Exception exception)
{
m_log.Fatal("Initialization of service failed",exception);
return false;
}
}
public bool Stop(HostControl hostControl)
{
m_log.Info("Server has closed");
return true;
}
}
should I just execute the .exe service file, or can I use my ServiceInitializer in some way? perhaps I could use nUnit's [SetUpFixture]?
Are there any Specflow best practices?
Let's consider what you want to test.
Do you need to test that Windows correctly runs services?
Do you need to test that Topshelf correctly starts services?
Or do you just want to test that GetAccount works?
I'll bet that you are using Topshelf to make your life easier, so do that and trust that their code works within windows. Its a valid assumption since there code will be used in many places and they probably have their own test suites, and if your assumption is wrong, then test it later when you find the problems.
So all you really need is
[BeforeFeature]
public void Background()
{
FeatureContext.Current["Host"] =new MyHostObject();
}
[When("I call GetAccount API method with password =\"(\.*)\"")]
public void WhenICallGetAccount(string password)
{
var host = (MyHostObject)FeatureContext.Current["Host"];
ScenarioContext.Current["Account"] = host.GetAccount(password);
}
[Then("the result should be success")]
public void ThenTheResultShouldBeSuccessful()
{
var account = (MyAccount)ScenarioContext.Current["Account"];
//assuming using Should;
account.ShouldNotBeNull();
}
I am writing some unit tests for the persistence layer of my C#.NET application. Before and after the tests of a test class execute, I want to do some cleaning up to erase possibly inserted dummy values, therefore, this cleaning up happens in methods marked with the attributes [ClassInitialize()] and [ClassCleanup()].
(I know that a better way would be to use an in-memory database, but it is not really doable so far as we depend on lots of stored procs....)
I would like to output some information about the results of the cleaning up, but I can not find a way to get the output in the test results with VISUAL Studio 2010.
This is what I am doing so far :
///... lots of stuff before ...
//global for the test run
private static TestContext context;
//for each test
private IRepository repo;
#region Initialisation and cleanup
/// <summary>
/// Execute once before the test-suite
/// </summary>
[ClassInitialize()]
public static void InitTestSuite(TestContext testContext)
{
context = testContext;
removeTestDataFromDb();
}
[ClassCleanup()]
public static void CleanupTestSuite()
{
removeTestDataFromDb();
}
private static void removeTestDataFromDb()
{
context.WriteLine("removeTestDataFromDb starting");
using (ISession session = NHibernateHelper.OpenSession())
{
IDbConnection cn = session.Connection;
IDbCommand cmd = cn.CreateCommand();
//remove anyt test data
cmd.CommandText = #"DELETE FROM SomeTable
WHERE somefield LIKE 'easyToFindTestData%Test'";
int res = cmd.ExecuteNonQuery();
context.WriteLine("removeTestDataFromDb done - affected {0} rows", res);
}
}
[TestInitialize()]
public void InitTest()
{
repo = new MyRepositoryImplementation();
}
[TestCleanup()]
public void CleanupTest()
{
//cleanup
repo = null;
}
#endregion
I'm trying to use context.WriteLine() ...
I also tried just using Console.WriteLine() with the same results.
How do you write to standard output in the ClassInitialize part and where can you access that output ?
The [ClassInitialize] and [ClassCleanup] run just once for all the tests in that class. You'd be better of using [TestInitialize] and [TestCleanUp] which run before and after each test. Also try wrapping the complete test in a database transaction. This way you can simply rollback the operation (by not committing the transaction) and your database stays in a consistent state (which is essential for trustworthy automated tests).
A trick I do for integration tests is to define a base class that all my integration test classes can inherit from. The base class ensures that each test is ran in a transaction and that this transaction is rolled back. Here is the code:
public abstract class IntegrationTestBase
{
private TransactionScope scope;
[TestInitialize]
public void TestInitialize()
{
scope = new TransactionScope();
}
[TestCleanup]
public void TestCleanup()
{
scope.Dispose();
}
}
Good luck.
The trace output from a ClassInitialize and ClassCleanup appears in the result summary.
You can access it by doing the following
Open the Test Results windw [ Test -> Windows -> Test Results ]
There should be a link named "Test run completed" on the top left corner of the [Test Results] window.
Click the clink
It should open a window with the header "Result Summary" and it will show the debug trace created during ClassInitialize and ClassCleanup
You can see the Console output on each test if you double-click the test method in the Test Results pane. It is also present in the .trx xml results file.
In addition, if you specify the "Define DEBUG constant", you can use the
System.Diagnostics.Debug.WriteLine("ClassInitialize Method invoked, yeah.");
.. which will end up in the "Output" pane.