5
votes

I have some unit tests that I'm writing for a WPF application, and as much as I've tried to avoid it, I have some code under test that instantiates a View. As soon as the view is instantiated, all the markup extensions, styles, etc are evaluated. To resolve this I've created a dummy Application and registered any required resources when the test assembly is initialized:

[TestClass]
public class AssemblyInitialize
{
    [AssemblyInitialize]
    public static void SetupTestAssembly(TestContext context)
    {
        if (Application.Current == null)
            new Application();

        var resources = new List<string>
            {
              "pack://application:,,,/AssemblyName;component/ResourceDictionary.xaml"
            };

       foreach(var resource in resources)
       {
           var uri = new Uri(resource);
           var dictionary = new ResourceDictionary { Source = uri };
           Application.Current.Resources.MergedDictionaries.Add(dictionary);
       }
    }
}

I've used this approach in the past, and it works ok.

I've run into a small snag with this approach. I have a few resources that use pack://siteoforigin: in the pack Uri, and when the tests instantiate this view I get an error about not being able to resolve the file.

The XAML:

<ResourceDictionary
   xmlns="...">

   <ImageBrush 
      x:Key="ResourceName"
      ImageSource="pack://siteoforigin:,,,/Resources/image.png" 
      />
</ResourceDictionary>

Error Message:

 Could not find a part of the path 'C:\\Solution\\TestResults\\Workspace_2012-03-01 14_54_29\\Resources\\image.png'

I've added the Resources directory as a deployment item, and I've confirmed that the image is the TestRun output directory. It seems that the AppDomain is operating one folder above where the location of my test assemblies, because the file is actually located at:

c:\Solution\TestResults\Workspace_2012-03-01 14_54_29\ Out \Resources\image.png

Any suggestions on how I can get the WPF Application to use the Out directory as it's primary folder?

2
Does it work to manually force your Appdomain's base folder? AppDomain.CurrentDomain.SetData("APPBASE", "FolderNameHere"); It seems like there is a better way, but my memory's failing me. This may be Good Enough (tm).ianschol
Been digging into this for hours. AppDomain.CurrentDomain.BaseDirectory returns the Out folder.bryanbcook

2 Answers

4
votes

This is because the AppDomain.BaseDirectory that is setup by your test runner doesn't have a trailing '/' character, this causes the code that resolves siteoforigin paths to lose the last directory in the path.

You can check this by looking at the result of the following code when running normally or in tests.

Console.WriteLine(AppDomain.CurrentDomain.BaseDirectory);

This has been recently fixed in NUnit (included in 2.6), but may still be an issue with other test runners.

If you're interested this is the equivalent of what the siteoforigin code is doing:

new Uri(new Uri(baseDirectory), "some/relative/path.jpg").LocalPath

Try that with and without a trailing slash in baseDirectory.

1
votes

I've successfully worked around the problem, as explained in the answer above, by changing the AppDomain.CurrentDomain.BaseDirectory via reflection:

private static void FixWpfPackSiteOfOriginReferences()
{
    // note: xunit does not add a trailing slash to the BaseDirectory path.
    // When you start the application, there's a trailing slash.
    // Also see:
    // - https://stackoverflow.com/a/9908449/684096
    // - https://github.com/dotnet/runtime/issues/7866#issuecomment-299761968

    var appDomainSetup = GetInstancePropertyValueViaReflection<AppDomainSetup>(
        AppDomain.CurrentDomain,
        "FusionStore");

    if (!appDomainSetup.ApplicationBase.EndsWith(
             Path.DirectorySeparatorChar.ToString()))
    {
        appDomainSetup.ApplicationBase += Path.DirectorySeparatorChar;
    }
}


private static T GetInstancePropertyValueViaReflection<T>(
     object instance,
     string propertyName)
{
    PropertyInfo property = GetInstancePropertyInfo(instance, propertyName);
    return (T)property.GetValue(instance);
}

private static PropertyInfo GetInstancePropertyInfo(
    object instance,
    string propertyName)
{
    Type instanceType = instance.GetType();
    PropertyInfo propertyInfo = instanceType
        .GetProperty(
            propertyName, 
            BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
    if (propertyInfo == null)
    {
        throw new ArgumentOutOfRangeException(
            nameof(propertyName),
            propertyName,
            FormattableString.Invariant(
                $"{instanceType} does not have a property '{propertyName}'"));
    }

    return propertyInfo;
}

This is done before creating the Wpf-App in the test, which itself, in my case, is scoped via xunit collection fixture.