Welcome Guest! To enable all features please Login or Register.

Notification

Icon
Error

Integration Testing with EF Code First and Migrations
ChuckBryan
#1 Posted : Tuesday, April 12, 2016 1:31:15 PM(UTC)
Rank: Newbie

Groups: Registered
Joined: 4/9/2016(UTC)
Posts: 3
Location: United States of America

Thanks: 1 times
Was thanked: 1 time(s) in 1 post(s)
I have been able to get NCrunching on integration test, but, after a few runs, I get errors about the database not being found and I need to delete the localdb using SQL Object Explorer. I believe I need to set the name of my MDF or Database (or both) to either a GUID or a ProcessId, but, I am not really sure where to do that. My class that does the database creation is as follows:

Code:
public class EfCreateDatabaseBehavior : Behavior<IDatabaseBehavior>
{
    private static bool _isIntialized;

    public override void SpecInit(IDatabaseBehavior instance)
    {
        try
        {
            if (_isIntialized) return;

            AppDomain.CurrentDomain.SetData("DataDirectory", Directory.GetCurrentDirectory());

            var strategy = new MigrateDatabaseToLatestVersion<ApplicationDbContext, Configuration>();

            Database.SetInitializer(strategy);

            using (var context = new ApplicationDbContext())
            {
                context.Database.Initialize(force: true);
            }

            _isIntialized = true;
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
        }
    }
}


Do I just need to pass in a name to the "new ApplicationDbContext()"?

Thanks

Chuck
Remco
#2 Posted : Tuesday, April 12, 2016 11:26:07 PM(UTC)
Rank: NCrunch Developer

Groups: Administrators
Joined: 4/16/2011(UTC)
Posts: 7,145

Thanks: 959 times
Was thanked: 1290 time(s) in 1196 post(s)
Hi Chuck,

Thanks for posting! The answer to your question is a bit complicated, depending upon how much effort you want to invest into your testing system. My assumption is that at the moment, this test is designed to run synchronously, with no parallel execution. You can read more about parallel execution here - http://www.ncrunch.net/documentation/concepts_parallel-execution.

I find it interesting that the test would complain about the underlying database being missing. This suggests to me that you are attempting to run database tests in parallel on the same system, and you have some code as part of the test that automatically deletes/tearsdown the database on completion of the run. This would then result in one test pulling the database out from under the other tests, which would then give errors.

The quickest and easiest way to solve this problem is to disable parallel execution for the solution, using the solution-level configuration setting.

A more effective way is to use ExclusivelyUsesAttribute to declare each database test as making use of the same resource. NCrunch will then avoid running them in parallel with each other.

The best solution is to rig up each parallel test process so it will use a different database for each process (I think this is what you were asking after). Specifying System.Diagnostics.Process.GetCurrentProcess().Id as the database name during its creation is a very effective way to do this, but you'll still need to have code that can clean up the databases after the test run completes. Because there is no reliable way for a test run to clean up after itself (i.e. it could be suddenly terminated at any time), this actually needs to be done at the start of a test run, by enumerating ALL databases on the database server using DDL, comparing this against the list of running processes on the current machine, then deleting all databases that do not have a corresponding process. This takes some effort to get up and running, but it is tremendously rewarding as database integration tests often take a long time to run, and NCrunch's parallel execution can greatly reduce the time required for an end-to-end run.
ChuckBryan
#3 Posted : Wednesday, April 13, 2016 11:12:25 AM(UTC)
Rank: Newbie

Groups: Registered
Joined: 4/9/2016(UTC)
Posts: 3
Location: United States of America

Thanks: 1 times
Was thanked: 1 time(s) in 1 post(s)
Remco - Thanks for the reply, you giving me more stuff to go back and check. The issue that I was getting was that the localdb was being created in the bin directory of one of the shadow folders. Are the "numbered" names in the folders the Process Id or something? Whenever a new numbered folder would be created, I would still have the previous database registered with localdb. When the new folder was created, and my code created the database with the same name, that was what was causing the error.

I think I read somewhere about advice on either name the database with a guid OR with the process id to keep it unique. Then, at somepoint, the database would need to be torn down and unregistered with localdb.

I did set the test up to run in Parallel and I did increase the thread count: is this the wrong configuration for integration tests?

Again, thanks for the reply. My Unit Tests are BUTTER! Love the NCrunch lifestyle and am considering upgrading my license. I am already planning on doing a demo at my company's monthly tech meetings and possible as a lighting talk at a local user group meeting.

Chuck


Remco
#4 Posted : Wednesday, April 13, 2016 12:43:12 PM(UTC)
Rank: NCrunch Developer

Groups: Administrators
Joined: 4/16/2011(UTC)
Posts: 7,145

Thanks: 959 times
Was thanked: 1290 time(s) in 1196 post(s)
Hi Chuck,

The numbered names of the NCrunch workspaces are just sequentially generated - they don't have a direct relationship with the processes used to run your tests. If your tests are set up to run in parallel (which yours are), you'll likely observe many test runner processes sharing the same workspace.

Storing the localdb on disk inside a workspace doesn't greatly change your approach - you'll still want a separate DB per test runner process. Using the current process ID in the generated database name is the most effective way to do this. Using a GUID will make it difficult to effectively clean up the databases, as you won't have an easy way to knowing which databases are in use at a given time (unless you just want to ram up against file locks).

Enabling parallel execution at solution level and turning up the max number of processing threads is the correct way to enable parallel execution. Provided you can cater for tests that share resources outside the test process (i.e. files on the file system and databases), NCrunch should be able to chew through your tests much faster with this switched on. Unless you're running a suite of really fast unit tests and no integration tests, I would say that parallel execution is required to get the best experience out of NCrunch.
1 user thanked Remco for this useful post.
ChuckBryan on 4/13/2016(UTC)
ChuckBryan
#5 Posted : Thursday, April 14, 2016 8:54:27 PM(UTC)
Rank: Newbie

Groups: Registered
Joined: 4/9/2016(UTC)
Posts: 3
Location: United States of America

Thanks: 1 times
Was thanked: 1 time(s) in 1 post(s)
Just a follow up...I think I got this worked out. What was causing me to stumble was figuring out where I needed to change my Connection String for my EF Db Context. Ultimately, I did this in my Db Context Constructor. I did take your advice and used the Process Id as the Name. During Tear Down, I look for the database name (again, using the process id), execute a command to put it in single use, then another to drop.

Code:
public class ApplicationDbContext : DbContext
{
	public ApplicationDbContext() : base(GetSqlConnection()) {}

	public static string GetSqlConnection()
	{
		string connectionString = ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString;

		if (Environment.GetEnvironmentVariable("NCrunch") == "1")
		{
			int processId = Process.GetCurrentProcess().Id;

			connectionString =
			string.Format(
				@"Data Source=(LocalDb)\MSSqlLocalDb;AttachDbFilename=|DataDirectory|\{0}mydatabase.mdf;Initial Catalog={0}_IntegrationSpecs;Integrated Security=True",
				processId);
		}

		return connectionString;
	}
}


and the code for tear down:
Code:
public override void AfterSpec(IDatabaseBehavior instance)
{
	if (Environment.GetEnvironmentVariable("NCrunch") != "1") return;
	int processId = System.Diagnostics.Process.GetCurrentProcess().Id;
		
	using (var connection = new SqlConnection(@"Data Source=(LocalDb)\MSSqlLocalDb;Integrated Security=True"))
	{
		connection.Open();
			
		var alterDatabaseCommand = new SqlCommand($"ALTER DATABASE [{processId}_IntegrationSpecs] SET SINGLE_USER WITH ROLLBACK IMMEDIATE", connection);
		alterDatabaseCommand.ExecuteNonQuery();

		var dropDatabase = new SqlCommand($"DROP DATABASE [{processId}_IntegrationSpecs]", connection);
		dropDatabase.ExecuteNonQuery();
	}
}


My initial tests appear to be working.

Thanks

Chuck
1 user thanked ChuckBryan for this useful post.
Remco on 4/14/2016(UTC)
Users browsing this topic
Guest
Forum Jump  
You cannot post new topics in this forum.
You cannot reply to topics in this forum.
You cannot delete your posts in this forum.
You cannot edit your posts in this forum.
You cannot create polls in this forum.
You cannot vote in polls in this forum.

YAF | YAF © 2003-2011, Yet Another Forum.NET
This page was generated in 0.045 seconds.
Trial NCrunch
Take NCrunch for a spin
Do your fingers a favour and supercharge your testing workflow
Free Download