Base solution for your next web application
Open Closed

Integration testing and including an implicit transaction... #326


User avatar
0
theedge created

Hi,

This is a cross post of [https://github.com/aspnetboilerplate/aspnetboilerplate/issues/616]). I am looking for a solution on how to do integration testing such that I can get a transaction for each test that is rolled back at the conclusion of each test.

I am posting here in case others are not monitoring the GitHub feed.


11 Answer(s)
  • User Avatar
    0
    hikalkan created
    Support Team

    I did test with localdb before. Thus, I could use SPs and views. I created a new DB file in disk for each test and deleted after the test. It is slow (1 or 2 seconds for each tests) but working :)

    I think rollback of transaction is not good approach. Because, transaction may throw exception on commit! If you rollback it, you will not see such problems. But it you really want it, just use IUnitOfWorkManager.Begin method in a using statement and write all your test code inside that. If you don't call Complete method, your transaction is automatically rolled back.

  • User Avatar
    0
    hikalkan created
    Support Team

    If you can not do it, I can search for the code I mentioned and share here.

  • User Avatar
    0
    theedge created

    Hi,

    Making a copy of the database to localdb is not an option for each test. Our production DB is a couple of hundred MB ;-). I will give your IUnitOfWorkManager.Begin approach a go and see how I go.

    transaction may throw exception on commit! If you rollback it, you will not see such problems. We are only concerned that our code / sp's which may already have their own transactions succeed so the fact that the outer transaction we are simulating fails will not be an issue.

  • User Avatar
    0
    hikalkan created
    Support Team

    One more thing.. why you're using your production database for testing? Generally we create a test database (empty database with initial test seed) for tests.

  • User Avatar
    0
    theedge created

    Hi,

    We are not using the production database. It is a copy of the production database with the required seed data.

  • User Avatar
    0
    hikalkan created
    Support Team

    That's better (and I thought it :)). But, you could create a test db with predefined test data. You may think that most real data is the PROD data. But, in unit test, we generally want to know the data we're testing (that simplifies assertions). Also, it's good that test data is relatively small since test should be fast. Anyway, it's your choise, I just wanted to say my opinion :)

  • User Avatar
    0
    theedge created

    Hi Halil,

    I have tried your suggestion with IUnitOfWork but no joy..... Here is my implementation:

    //My Repository
    public interface IOzCpReferencePortRepository : IRepository<Reference_PortModel, string>
    {
    }
    
    //My application service
    public interface IOzCpReferencePortService : IApplicationService
    {
        void DonoAddPort();
    }
    
    
    
    //Adapter class to make ABP play nice with NUnit
    public class AbpApplicationServiceTestsAdapter : OzAbpTestsAdapterBase
    {
        protected override void AddModules(ITypeList<AbpModule> aModules)
        {
            base.AddModules(aModules);
    
            //Add testing modules. Dependant modules are automatically added.
            aModules.Add<OzCruisingPlatformApplicationModule>();
            aModules.Add<OzCruisingPlatformDataModule>();
        }
    }
    
    //My NUnit test class
    public class OzCpReferencePortService : OzCruisingPlatformAppServiceBase, IOzCpReferencePortService
    {
        private readonly IOzCpReferencePortRepository _Repository;
    
        /// <summary>
        ///     Class constructor
        /// </summary>
        /// <param name="aRepository">Repository associated with the service</param>
        public OzCpReferencePortService(IOzCpReferencePortRepository aRepository)
        {
            //Save params supplied
            _Repository = aRepository;
    
            //Configure auto mapper
            ConfigureAutoMapper();
        }
    
       public void DonoAddPort()
        {
            //Dummy for testing
            _Repository.Insert(new Reference_PortModel
                               {
                                   CountryCode = "AU",
                                   MarketName = "Dono",
                                   PortCode = "DDD",
                                   PortName = "Dono",
                                   VisaRequired = true
                               });
        }
    }
    
    [TestFixture]
    public class ReferencePortTests : OzTestsBase
    {
        private AbpApplicationServiceTestsAdapter _AbpApplicationTestsAdapter;
        private IOzCpReferencePortService _ReferencePortService;
        private IUnitOfWork _UnitOfWork;
    
        /// <summary>
        ///     Called at the commencement of all the unit tests.
        /// </summary>
        [TestFixtureSetUp]
        public void Init()
        {
            //Spin up the adapter class to register all the necessary with the IocManager
            _AbpApplicationTestsAdapter = new AbpApplicationServiceTestsAdapter();
    
            //Create our reference to the reference port service
            _ReferencePortService = _AbpApplicationTestsAdapter.LocalIocManager.Resolve<IOzCpReferencePortService>();
        }
    
        [Test]
        public void Dono()
        {
            _ReferencePortService.DonoAddPort();
        }
    	
        /// <summary>
        ///     Called before each test.
        /// </summary>
        [SetUp]
        public void TestSetup()
        {
            //Ensure all interactions run within a transaction of their own so that our changes are not persisted to the database
            _UnitOfWork =  _AbpApplicationTestsAdapter.LocalIocManager.Resolve<IUnitOfWork>();
            _UnitOfWork.Begin(new UnitOfWorkOptions());
        }
    
        /// <summary>
        ///     Called after each test.
        /// </summary>
        [TearDown]
        public void TestTearDown()
        {
            _UnitOfWork.Dispose();
        }
    }
    

    I spin up a transaction in TestSetup() and dispose of it in TestTearDown which are called on either side of the test Dono(). However the record added in Dono() still exists in the database after the unit of work has been disposed off (ie rolled back). I cannot use it in a using statement other than putting that in each test which I don't want to do, hence the explicit call to Dispose().

    Can you shed any light on this?

  • User Avatar
    0
    hikalkan created
    Support Team

    Use IUnitOfWorkManager to begin a new transaction, not directly IUnitOfWork (see example: <a class="postlink" href="http://www.aspnetboilerplate.com/Pages/Documents/Unit-Of-Work#iunitofworkmanager">http://www.aspnetboilerplate.com/Pages/ ... orkmanager</a>)

    ... in the setup ...
    
    var uowManager = _AbpApplicationTestsAdapter.LocalIocManager.Resolve<IUnitOfWorkManager>();
    _uow = uowManager.Begin()
    
    ... in the tear down...
    
    _uow.Dispose();
    
  • User Avatar
    0
    theedge created

    Hi Halil,

    Doh! Sorry that was a dumb mistake on my part. Doing that I now successfully have the test running in a transaction and no changes being made to the database, BUT.... I cannot see any of my changes made in the transaction when I make a successive call.

    [SetUp]
            public void TestSetup()
            {
                //Ensure all interactions run within a transaction of their own so that our changes are not persisted to the database
                IUnitOfWorkManager unitOfWorkManager = _AbpApplicationTestsAdapter.LocalIocManager.Resolve<IUnitOfWorkManager>();
                _UnitOfWork = unitOfWorkManager.Begin(new UnitOfWorkOptions
                                                      {
                                                          IsolationLevel = IsolationLevel.ReadUncommitted
                                                      });
            }
    

    and now the test that adds a record and then uses the service to find the record:

    [Test]
            public void Dono()
            {
                _ReferencePortService.DonoAddPort();
    
                OzCpGetPortOutput port = _ReferencePortService.GetPort(new OzCpGetPortInput
                                                                       {
                                                                           PortCode = "DDD"
                                                                       });
    
                //The following should NOT be possible as I have just inserted a PortCode "DDD" via DonoAddPort()
                if (port.Item == null)
                {
                       //Get in here all the time no matter what I set the UoW isolation level to
                }
            }
    

    The only time GetPort() returns the record is if I do not do unitOfWorkManager.Begin() ie with NO transaction for the test. So even for an isolation level allowing dirty reads I cannot get the record.

    What am I missing here?

  • User Avatar
    0
    hikalkan created
    Support Team

    Hi,

    Call _UnitOfWork.SaveChanges(). Otherwise, EntityFramework itself does not insert your entity to database. Don't worry, it will be rolled back when uow is disposed without calling .Complete() method.

  • User Avatar
    0
    theedge created

    Thanks Halil, missed the SaveChanges() bit. I now have this working as I want it.