The instance name is defined as:
if (scopeSuffix == null)
{
return typeof(TDbContext).Name;
}
return $"{typeof(TDbContext).Name}_{scopeSuffix}";
That InstanceName is then used to derive the data directory. In order:
- If
LocalDBData
environment variable exists then useLocalDBData\InstanceName
. - Use
%TempDir%\LocalDb\InstanceName
.
There is an explicit registration override that takes an instance name and a directory for that instance:
var sqlInstance = new SqlInstance(
name: "theInstanceName",
buildTemplate: TestDbBuilder.CreateTable,
directory: @"C:\LocalDb\theInstance"
);
var sqlInstance = new SqlInstance<TheDbContext>(
constructInstance: builder => new TheDbContext(builder.Options),
name: "theInstanceName",
directory: @"C:\LocalDb\theInstance");
When using azure hosted machines for build agents, it makes sense to use the agent temp directory as defined by the AGENT_TEMPDIRECTORY
environment variable. The reason is that the temp directory is located on a secondary drive. However this drive has some strange permissions that will cause run time errors, usually manifesting as a SqlException with Could not open new database...
. To work around this run the following script at machine startup:
$paths = @('D:\Agent', 'D:\Agent\Work', 'D:\Agent\Work\_Temp')
$paths | % {
$d = [System.IO.Directory]::CreateDirectory($_)
$acl = Get-Acl $d.FullName
$ar = new-object System.Security.AccessControl.FileSystemAccessRule("Everyone", "FullControl", "ContainerInherit, ObjectInherit", "None", "Allow")
$acl.AddAccessRule($ar)
Set-Acl $d.FullName -AclObject $acl
}
A design goal is to have an isolated database per test. To facilitate this the SqlInstance.Build
method has a convention based approach. It contains the following parameters:
testFile
: defaults to the full path of the source file that contains the caller via CallerFilePathAttribute.memberName
: defaults to the method name of the caller to the method via CallerMemberName.databaseSuffix
: an optional parameter to further uniquely a database name when thetestFile
andmemberName
are not sufficient. For example when using parameterized tests.
The convention signature is as follows:
/// <summary>
/// Build database with a name based on the calling Method.
/// </summary>
/// <param name="testFile">The path to the test class. Used to make the database name unique per test type.</param>
/// <param name="databaseSuffix">For Xunit theories add some text based on the inline data to make the db name unique.</param>
/// <param name="memberName">Used to make the db name unique per method. Will default to the caller method name is used.</param>
public Task<SqlDatabase> Build(
[CallerFilePath] string testFile = "",
string? databaseSuffix = null,
[CallerMemberName] string memberName = "")
With these parameters the database name is the derived as follows:
public static string DeriveDbName(string? databaseSuffix, string memberName, string testClass)
{
if (databaseSuffix == null)
{
return $"{testClass}_{memberName}";
}
return $"{testClass}_{memberName}_{databaseSuffix}";
}
If full control over the database name is required, there is an overload that takes an explicit name:
/// <summary>
/// Build database with an explicit name.
/// </summary>
public async Task<SqlDatabase> Build(string dbName)
Which can be used as follows:
using (var database = await sqlInstance.Build("TheTestWithDbName"))
{
using (var database = await sqlInstance.Build("TheTestWithDbName"))
{