3

I've used this documentation for my integration tests: asp.net core integration tests and this is my simplified integration test:

 public class ApplicationControllerTests : IClassFixture<CustomWebApplicationFactory<Startup>>
 {
     private readonly CustomWebApplicationFactory<Startup> _factory;

     public ApplicationControllerTests(CustomWebApplicationFactory<Startup> factory)
     {
           _factory = factory;
     }

    [Fact]
    public async Task AcceptOffer_WhenCalled_ShouldReturnSuccess()
    {
        var httpClient = factory.CreateClient();

        var acceptOfferRequest = new AcceptOfferRequest
        {
            OfferId = 1,
            OfferType = 1
        }.ToJsonStringContent();

        var response = await httpClient.PostAsync("/api/v1/Application/AcceptOffer", acceptOfferRequest);

        response.EnsureSuccess();

        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
    }
}

As you can see I am sending an HTTP request to "/api/v1/Application/AcceptOffer" API, which updates some entities in the database and returns status code 200 if everything is ok.

After the test, I want to clean the database as it was before the test. For that, I want to put my tests inside the transaction and rollback after each test finishes. Is it possible with the current implementation? Dropping and recreating a database is costly, that's why I want to put my tests inside transactions.

I am using MS SQL database with entity framework ORM.

One solution is to use a singleton of DbContext and then wrap all tests inside transactions but the problem is SQL does not support nested transactions and if API uses transactions too it will throw a runtime exception. Another solution is to use C#'s TransactionScope but it does not work with SQL transactions and does not work with different threads.

pfloyd
  • 266
  • 2
  • 10
  • You can use an in-memory database provider. – Ian Kemp Nov 06 '20 at 11:16
  • 2
    I prefer an exact copy of the production database. in-memory db may work differently – pfloyd Nov 06 '20 at 12:21
  • What's inside your `CustomWebApplicationFactory`? Do you override anything from your DbContext setup (Entity Framework) or do you use another ORM? – kristofke Nov 06 '20 at 17:36
  • I am using entity-framework and do not override anything. Just changing the hosting environment to read different app settings. – pfloyd Nov 07 '20 at 09:27
  • One solution is to use a singleton of DbContext and then wrap all tests inside transactions but the problem is sql does not support nested transactions and C#'s TransactionScope does not work for different threads. – pfloyd Nov 07 '20 at 09:30
  • Use tear down process. Dont use singleton for Dbcontext. – maxspan Nov 11 '21 at 12:53

2 Answers2

0

Okay, As I find out there is no nice way or possible to solve this problem using transactions. Instead of transactions, I wrote global scripts where I manually truncate all tables and execute it after each test. It's obviously faster than dropping and recreating the database.

pfloyd
  • 266
  • 2
  • 10
0

I don't think it's possible to pass a db transaction from test to the webapp without any adjustments in the app itself. Unless TestServer hosting app in the same process might be of some help here.

One possible idea (not sure I like it though) would involve following application changes:

  • introduce special test-only endpoint in your app, which will accept an identifier (e.g guid), start a transaction and save that transaction by identifier in the app state;
  • attach this transaction identifier to every request executed in the test and write a middleware, which will retrieve the existing transaction from the app state and attach it to the request's DbContext;
  • introduce another endpoint to revert a transaction by its id;
  • make sure that all this logic is possible for Test environment only.

If you decide to try it, I suggest that you check how TestServer works and if it might be somehow used to implement this same idea without introducing dedicated endpoints for transaction creation/rollback.

And you're correct, that it's not possible to use this approach to test logic, which needs transactions on its own as transactions can't be nested in general. So optimization will be applicable just for the part of your tests, rest will need to execute cleanup differently.

But if I were you, I'd rather use other means of db restoring than transactions for this case. Sth like db snapshots or insert/delete scripts – the route you actually went.

Having that said, I suggest you to checkout Reseed library I'm currently developing, which is able to take care of db restoring by generating insert and delete scripts and executing them fast. There is also Respawn library for db cleanup, which is also suitable for your case.


Update

According to this question and replies it's possible to implement a behavior very close to the desired with use of TransactionScope type. It works as intended together with TestServer, but has it's downsides as already mentioned by the topic starter.

Uladzislaŭ
  • 1,680
  • 10
  • 13