How to seed data only once before running all tests using MSTest - c#

I have bunch of tests class such as CustomerTest, ProductTest, VendorTest and so on. I'm using in-memory Database and I would like to seed all data that I need to ONLY ONCE before running all these Tests above but have no idea how to do it.
I have CustomWebApplicationFactory class
public class CustomWebApplicationFactory<TProgram> : WebApplicationFactory<Program>
{
public ApplicationContext context { get; set; }
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureTestServices(services => {
var descriptor = services.SingleOrDefault(d => d.ServiceType == typeof(DbContextOptions<ApplicationContext>));
if (descriptor != null)
services.Remove(descriptor);
services.AddDbContext<ApplicationContext>(options => options
.UseInMemoryDatabase("testDB")
);
var serviceProvider = services.BuildServiceProvider();
using (var scope = serviceProvider.CreateScope())
{
var scopedServices = scope.ServiceProvider;
var db = scopedServices.GetRequiredService<ApplicationContext>();
db.Database.EnsureDeleted();
db.Database.EnsureCreated();
SeedData(db); //seeding all data to all table
}
});
}
}
My Tests class
[TestClass]
public class VendorTest : BaseTest
{
[TestMethod]
public async Task AddVendor()
{
var content = JsonSerializer.Serialize(
new
{
name = "VENDORTEST"
}
);
var response = await _httpClient.PostAsync(
"vendors",
new StringContent(content, Encoding.UTF8, "application/json")
);
Assert.IsTrue(response.IsSuccessStatusCode);
}
[TestMethod]
public async Task GetVendor()
{
var response = await _httpClient.GetAsync("vendors");
string result = await response.Content.ReadAsStringAsync();
Assert.IsNotNull(response);
}
}
Base Test
public class BaseTest
{
protected static CustomWebApplicationFactory<Program> _webAppFactory = new CustomWebApplicationFactory<Program>();
protected static HttpClient _httpClient = _webAppFactory.CreateDefaultClient();
[AssemblyInitialize]
public void Initialize()
{
//not sure if BaseClass is the way to do it
//_httpClient = _webAppFactory.CreateDefaultClient();
}
}
Everytime a TestMethod is running, it will reseed all of the data due to CustomWebApplicationFactory. Any idea how to do it only once?

First of all: I dislike your idea so much, that I hesitated to show you a possible approach. I'll explain that after the code.
If you need something, that runs only once, static things come to mind. So, you could use a static/singleton factory to create your in-memory database like this:
public class TestDbFactory
{
private static TestDbFactory instance;
private readonly ApplicationContext applicationContext;
private TestDbFactory()
{
var databaseName = Guid.NewGuid().ToString();
var inMemoryContextOptions = new DbContextOptionsBuilder<ApplicationContext>()
.UseInMemoryDatabase(databaseName)
.Options;
this.applicationContext = new ApplicationContext(inMemoryContextOptions);
this.InitDatabase();
}
public static TestDbFactory Instance => CreateOrReuseInstance();
public ApplicationContext ApplicationContext => this.applicationContext;
private static TestDbFactory CreateOrReuseInstance()
{
if (instance != null) return instance;
var semaphore = new SemaphoreSlim(1, 1);
instance = instance ?? new TestDbFactory();
semaphore.Release(1);
return instance;
}
private void InitDatabase()
{
// ensure deleted & created & seeded for this.applicationContext
}
}
Now you can either use TestDbFactory.Instance.ApplicationContext to pass the database to the systems under test or you can use the factory pattern of the dependency injection setup to provide the context:
services.AddScoped(_ => TestDbFactory.Instance.ApplicationContext);
What I don't like about your idea:
Even for a single test you have to seed the whole database.
You can't do write test, because then tests don't have a predictable setup (it might be or not, that the db contents have changed before a test runs).
To me a test also serves as documentation. This is not possible, if the database content relevant for a specific test can't be determined from the test.
Adding new tests and updating the seeding will become a nightmare as you (let alone a whole team) have to verify the new data vs. all existing tests.
You won't save much typing as you still have to data for every test.
So, to me a better approach would be to use an in-memory database in every test class (a test class contains all tests for a single method). It would be ok to provide a basic seeding common to all tests in the test class, but specific data setup for a test should go with the test.
I hope that this post helps in one way or the other.

Related

Writing Xunit test cases in C#

I am kind of learning to write unit test cases and I am using Xunit framework. I have a scenario where I would like to write a test case to test different scenario in my cosmos db emulator. To do that I am trying to create a database, container and insert few test data in my cosmos db emulator and then write my facts and also delete it once test cases are completed...below is the code which I figured out from internet, would like to know if I am doing it correctly... and where can I start writing my test cases.
namespace Project.Tests
{
public class DatabaseFixture : IDisposable
{
private static readonly string CosmosEndpoint = "https://localhost:8081";
private static readonly string EmulatorKey = "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==";
private static readonly string DatabaseId = "Recordings";
private static readonly string RecordingCollection = "testdata";
public DatabaseFixture()
{
var client = new DocumentClient( new Uri( CosmosEndpoint ), EmulatorKey,
new ConnectionPolicy
{
ConnectionMode = ConnectionMode.Direct,
ConnectionProtocol = Protocol.Tcp
} );
var databaseCreationResult = client.CreateDatabaseAsync( new Database { Id = DatabaseId } ).Result;
var collectionCreationResult = client.CreateDocumentCollectionAsync( UriFactory.CreateDatabaseUri( DatabaseId ),
new DocumentCollection { Id = RecordingCollection } ).Result;
var recData = new Recordings { Id = "Test" };
var itemResult = client
.CreateDocumentAsync(
UriFactory.CreateDocumentCollectionUri( DatabaseId, RecordingCollection ), recData )
.Result;
var document = client
.ReadDocumentAsync(
UriFactory.CreateDocumentUri( DatabaseId, RecordingCollection, itemResult.Resource.Id ) )
.Result;
Recordings site = (dynamic)document.Resource;
}
public void Dispose()
{
// ... clean up test data from the database ...
throw new NotImplementedException();
}
}
public class Recordings
{
public string Id { get; set; }
}
public class MyDatabaseTests : IClassFixture<DatabaseFixture>
{
DatabaseFixture fixture;
public MyDatabaseTests( DatabaseFixture fixture )
{
this.fixture = fixture;
}
// ... write tests, using fixture.Db to get access to the database server ...
}
}
Be careful that using a web API is not really part of the Unit Test philosophy. A Unit Test is usually expected to be independent from external interaction.
You can still use xUnit to peform your testing, but you are not in a UNIT test context.
If you have access to the code behind the service, you could Unit Test it without the Web layer. (as an exemple, you can Unit test directly the REST controller class.)
If you ignore this point, I think the response is already in your question.
You can directly write your tests in the test class.
public class MyDatabaseTests : IClassFixture<DatabaseFixture>
{
DatabaseFixture fixture;
public MyDatabaseTests( DatabaseFixture fixture )
{
this.fixture = fixture;
}
// Write test method here
[Fact]
private void MyTestMethod()
{
// Prepare Test
/// Prepare your test data here.
// Execute Test
/// Execute your test operation here.
// Validate Test
/// Use Assert methods here.
/// Assert.True(....);
}
}

Is this the correct way to get context in xunit tests .NET Core?

In my test I have to have access to database context (real database, it's not in memory). Because after post I want to ensure that record has been saved in database.
This is what I have, it works perfectly but I don't have enough experiance to be sure that this is correct approach for getting context.
public class ValuesControllerTests : TestHostFixture
{
[Theory]
[InlineData("/values/sample")]
public async Task Sample_WhenCreatingSampleData_ThenIsAddedToDatabase(string url)
{
// given
var command = new AddSampleCommand { Name = "TestRecord" };
// when
var httpResponse = await Client.PostAsJsonAsync(url, command);
// then
httpResponse.EnsureSuccessStatusCode();
using (var dbContext = GetContext())
{
dbContext.Samples.FirstOrDefault(x => x.Name == "TestRecord").ShouldNotBeNull();
}
}
}
public abstract class TestHostFixture : IClassFixture<CustomWebApplicationFactory<Startup>>
{
protected readonly HttpClient Client;
private readonly CustomWebApplicationFactory<Startup> _factory;
protected TestHostFixture()
{
_factory = new CustomWebApplicationFactory<Startup>();
Client = _factory.CreateClient();
}
protected MyContext GetContext()
{
return _factory.Server.Host.Services.CreateScope().ServiceProvider.GetService<MyContext>();
}
}
So just to sum up - in test I'm getting Context by:
using (var dbContext = GetContext())
{
dbContext.Samples.FirstOrDefault(x => x.Name == "TestRecord").ShouldNotBeNull();
}
And GetContext method:
protected MyContext GetContext()
{
return _factory.Server.Host.Services.CreateScope().ServiceProvider.GetService<MyContext>();
}
Please let me know if this is fine, or maybe I should refactor it somehow because of some potential issue in future.
Generally, this is fine: You definitely should make use of the host’s DI container to retrieve the database context. Since the database context is scoped, it is also correct to create a new service scope to retrieve the context.
However, since the DI container is what usually manages the lifetime of the objects it creates, you should leave the disposal of the database context up to the DI container, and instead dispose of the service scope.
While it will probably not matter much in a unit test, which will be cleaned up quickly anyway (and since you are disposing the context you also won’t leak database connections), it’s still a better style and safer in the long run.
So dispose the service scope instead:
// …
httpResponse.EnsureSuccessStatusCode();
using (var scope = Host.Services.CreateScope())
{
var dbContext = scope.ServiceProvider.GetService<MyContext>();
var item = dbContext.Samples.FirstOrDefault(x => x.Name == "TestRecord");
item.ShouldNotBeNull();
}
Improving on the implementation provided by poke, you could consider creating a delegate to handle the proper disposal of the created scope.
For example
protected void GetContext(Action<MyContext> test) {
using(var scope = _factory.Server.Host.Services.CreateScope()) {
var context = scope.ServiceProvider.GetRequiredService<MyContext>();
test(context);
}
}
When exercising your test simple call the delegate
[Theory]
[InlineData("/values/sample")]
public async Task Sample_WhenCreatingSampleData_ThenIsAddedToDatabase(string url) {
// given
var command = new AddSampleCommand { Name = "TestRecord" };
// when
var httpResponse = await Client.PostAsJsonAsync(url, command);
// then
httpResponse.EnsureSuccessStatusCode();
GetContext(dbContext => {
var item = dbContext.Samples.FirstOrDefault(x => x.Name == "TestRecord");
item.ShouldNotBeNull();
});
}

xUnit test asp net core Mapper not initialized

I am testing this method that in the end does mapping to my DTO
public async Task<IActionResult> Get()
{
var currencies = await _repository.GetCurrencies().ToListAsync().ConfigureAwait(false);
if (currencies.Count == 0)
return NoContent();
return Ok(currencies.ToDto());
}
In return of this method I get the following error:
Message: System.InvalidOperationException : Mapper not initialized.
Call Initialize with appropriate configuration. If you are trying to
use mapper instances through a container or otherwise, make sure you
do not have any calls to the static Mapper.Map methods, and if you're
using ProjectTo or UseAsDataSource extension methods, make sure you
pass in the appropriate IConfigurationProvider instance.
I use a static method to do auto mapper
public static List<CurrencyDTO> ToDto(this List<Currency> model)
{
return Mapper.Map<List<Currency>, List<CurrencyDTO>>(model);
}
When I run only this test it passes but when I squeeze everyone it speaks
I tried already make a builder initialize mapper but continue error, test only passes if it is run individually
public class CurrenciesControllerTest
{
public CurrenciesControllerTest()
{
AutoMapperConfig.RegisterMappings();
}
private Mock<IReimbursementRepository> _repository = new Mock<IReimbursementRepository>();
[Fact]
public async Task Should_return_all_currencies()
{
var mock = Currencyfactory().AsQueryable().BuildMock();
_repository.Setup(x => x.GetCurrencies()).Returns(mock.Object);
var controller = new CurrenciesController(_repository.Object);
var response = await controller.Get().ConfigureAwait(false) as OkObjectResult;`enter code here`
Assert.Equal(response.StatusCode, (int)HttpStatusCode.OK);
}
}
my solution, configure AutoMapperConfig
public class AutoMapperConfig
{
public static object thisLock = new object();
public static void Initialize()
{
lock (thisLock)
{
AutoMapper.Mapper.Reset();
AutoMapper.Mapper.Initialize(cfg => { });
}
}
}
}

Moq: setup a generic method with mocked parameters

I've been trying to write a few tests in NUnit for my generic methods, without success. I hope I can make my situation clear, since I had to heavily paraphrase my code.
DoBusinessLogic() is the method I want to test. It calls two other methods from the base class:
public class MySvc : BaseSvc, IMySvc
{
private readonly IMyRepo _repo;
private readonly IMyConnectorClass _connector
public MySvc(IMyRepo repo) : base(repo)
{
_repo = repo;
_connector = _repo.GetConnector();
}
public async Task DoBusinessLogic(int id1, int id2){
bool doesFirstObjectExist = await CheckMainObjExists<Foo>(id1, _connector);
await CheckSubObjExists<Bar>(id2, _connector);
// further business logic
}
}
Those methods, in turn, call the same repository method, but have different logic after it:
public abstract class BaseSvc : IBaseSvc
{
private readonly IBaseRepo _repo
protected BaseSvc(IBaseRepo repo)
{
_repo = repo;
}
protected async Task<bool> CheckMainObjExists<T>(int? id, IMyConnectorClass connector)
{
return await _repo.GetObjectByIdAsync<T>(Id, connector) != null;
}
protected async Task CheckSubObjExists<T>(int? id, IMyConnectorClass connector)
{
if (await _repo.GetObjectByIdAsync<T>(Id, connector) == null)
{ throw new Exception("Object not found!"); }
}
}
Next, I want to write unit a test for DoBusinessLogic() in the MySvc class. Unfortunately, it seems I can't mock the responses from the repository.
[TestFixture]
public class MySvcTests
{
private MySvc _svc;
private Mock<IMyRepo> _repoMock;
private Mock<IMyConnectorClass> _connectorMock;
[SetUp]
public void SetUp()
{
_repoMock = new Mock<IMyRepo>() {};
_connectorMock = new Mock<IMyConnectorClass>();
_repo.SetUp(r => r.GetConnector()).Return(_connectorMock.Object);
_svc = new MySvc(_repoMock);
}
/*
My intent in this test, is to make CheckMainObjExists() pass,
but make CheckSubObjExist() fail.
*/
[Test]
public async Task DoBusinessLogic_If2ndObjectNotExist_ThrowException()
{
// This should return an object
_repoMock.Setup(r => r.GetObjectByIdAsync<Foo>(It.IsAny<int>(), _connectorMock.Object))
.ReturnsAsync(new Foo());
// This should return null
_repoMock.Setup(r => r.GetObjectByIdAsync<Bar>(It.IsAny<int>(), _connectorMock.Object))
.ReturnsAsync((Bar) null);
Assert.Throws<Exception>(await _svc.DoBusinessLogic());
}
}
However, when I run the test, both methods that I set up for my mock repo return null, whereas I expect a "true" from the first.
I do not know where the problem is situated, but I have my suspicions:
Is it possible to setup a method, using a mocked object as a parameter? In this case, is it possible to use _connectorMock.Object as a setup parameter?
Is it possible to setup the same generic method multiple times, but for a different type each time? It's first setup for Foo, then for Bar.
I just tested this code and it runs as expected. Now I had to make a lot of assumptions just to get the code to compile and run which would mean that my test of your code is flawed as I may have fixed something that was omitted in your example.
I made no changes to your test setup code, which worked.
[TestClass]
public class MySvcTests {
[TestMethod]
[ExpectedException(typeof(Exception))]
public async Task DoBusinessLogic_If2ndObjectNotExist_ThrowException() {
var _repoMock = new Mock<IMyRepo>() { };
var _connectorMock = new Mock<IMyConnectorClass>();
_repoMock.Setup(r => r.GetConnector()).Returns(_connectorMock.Object);
var _svc = new MySvc(_repoMock.Object);
// This should return an object
_repoMock.Setup(r => r.GetObjectByIdAsync<Foo>(It.IsAny<int>(), _connectorMock.Object))
.ReturnsAsync(new Foo());
// This should return null
_repoMock.Setup(r => r.GetObjectByIdAsync<Bar>(It.IsAny<int>(), _connectorMock.Object))
.ReturnsAsync((Bar)null);
await _svc.DoBusinessLogic(0, 0);
}
}

How to share in memory database using TestServer and data seeding class to run Integration Tests in memory

I recently started using TestServer class to self-host and bootstrap an Aspnet Core API to run Integration Tests without a dedicated running environment.
I love the way it works and using custom environment name, I decided to control the way the EF Context is created, switching from SQL Server to In-Memory database.
Problem is that in order to seed the data necessary to run the tests via API requests, would be very expensive for both coding and running time.
My idea is to create a class or a simple framework to seed the data necessary by each test, but to do so I need to use the same in-memory database which is initialized with the API stack by the TestServer.
How is it possible to do so?
In the first place is important to understand that for better testing in replacement to a relational database such SQL Server, In Memory database is not ideal.
Among the various limitations, it does not support foreign key constraints.
A better way to use an in-memory database is to use SQLite In-Memory mode.
Here is the code I used to setup the TestServer, Seed the Data and register the DB Context for the Dependency Injection:
TestServer
public class ApiClient {
private HttpClient _client;
public ApiClient()
{
var webHostBuilder = new WebHostBuilder();
webHostBuilder.UseEnvironment("Test");
webHostBuilder.UseStartup<Startup>();
var server = new TestServer(webHostBuilder);
_client = server.CreateClient();
}
public async Task<HttpResponseMessage> PostAsync<T>(string url, T entity)
{
var content = new StringContent(JsonConvert.SerializeObject(entity), Encoding.UTF8, "application/json");
return await _client.PostAsync(url, content);
}
public async Task<T> GetAsync<T>(string url)
{
var response = await _client.GetAsync(url);
response.EnsureSuccessStatusCode();
var responseString = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<T>(responseString);
}
}
Data Seeding (Helper class)
public class TestDataConfiguration
{
public static IMyContext GetContex()
{
var serviceCollection = new ServiceCollection();
IocConfig.RegisterContext(serviceCollection, "", null);
var serviceProvider = serviceCollection.BuildServiceProvider();
return serviceProvider.GetService<IMyContext>();
}
}
Data Seeding (Test class)
[TestInitialize]
public void TestInitialize()
{
_client = new ApiClient();
var context = TestDataConfiguration.GetContex();
var category = new Category
{
Active = true,
Description = "D",
Name = "N"
};
context.Categories.Add(category);
context.SaveChanges();
var transaction = new Transaction
{
CategoryId = category.Id,
Credit = 1,
Description = "A",
Recorded = DateTime.Now
};
context.Transactions.Add(transaction);
context.SaveChanges();
}
DB Context registration (In IocConfig.cs)
public static void RegisterContext(IServiceCollection services, string connectionString, IHostingEnvironment hostingEnvironment)
{
if (connectionString == null)
throw new ArgumentNullException(nameof(connectionString));
if (services == null)
throw new ArgumentNullException(nameof(services));
services.AddDbContext<MyContext>(options =>
{
if (hostingEnvironment == null || hostingEnvironment.IsTesting())
{
var connection = new SqliteConnection("DataSource='file::memory:?cache=shared'");
connection.Open();
options.UseSqlite(connection);
options.UseLoggerFactory(MyLoggerFactory);
}
else
{
options.UseSqlServer(connectionString);
options.UseLoggerFactory(MyLoggerFactory);
}
});
if (hostingEnvironment == null || hostingEnvironment.IsTesting())
{
services.AddSingleton<IMyContext>(service =>
{
var context = service.GetService<MyContext>();
context.Database.EnsureCreated();
return context;
});
} else {
services.AddTransient<IMyContext>(service => service.GetService<MyContext>());
}
}
The key is the URI file string used to create the SQLite connection:
var connection = new SqliteConnection("DataSource='file::memory:?cache=shared'");

Categories

Resources