Fake calling Namespace to test reflection

243 views Asked by At

I have a piece of code that gets a specific portion of the namespace from the calling assembly. Now I want to unit test this code. Is there a way to fake the name of the calling namespace using NUnit without implementing the NUnit testcase in that particular namespace?

Here is the method I want to test:

public static string FindCallingNameSpace()
{
   var stackTrace = new StackTrace();
   var stackFrames = stackTrace.GetFrames();
   if (stackFrames != null)
   {
       int nrFrames = stackFrames.Length;
       for (int i = 0; i < nrFrames; i++)
       {
           var methodBase = stackTrace.GetFrame(i).GetMethod();
           var Class = methodBase.ReflectedType;
           if (Class != null && Class.Namespace != null && Class.Namespace != "Foo.Common.WebService")
           {
               var Namespace = Class.Namespace.Split('.'); 
               return Namespace[1];
           }
       }
    }
    throw new Exception("Can't determine calling namespace! Need this to determine correct api url to call!");
}

An example would be: Bar.ExampleNs.SomeMethod() calls Foo.Common.WebService.CallApi() which itself calls the method above to retrieve the namespace from SomeMethod(). The result then would be "ExampleNs".

Now is it possible to create an NUnit UnitTest that is coded in the namespace MyUnitTests.ApiTest.TestNameSpace() but inside Foo.Common.WebService the call appears to come from Bar.ExampleNs.SomeMethod() so I can test for "ExampleNs"?

1

There are 1 answers

0
forsvarir On BEST ANSWER

I think by far the simplest way of achieving what you're after is to just create a call forwarder and call the FindCallingNamespace method via the forwarder. So, assuming that the FindCallingNamespace method is in a class CallerStuff you create this:

namespace SomeNameSpace.ToTest {
    static class RemoteCaller {
        static public string Run() {
            return CallerStuff.FindCallingNameSpace();
        }
    }
}

Then in your test you call RemoteCaller.Run, rather than CallerStuff.FindCallingNamespace.

However, you mentioned having Parameterized Tests, so presumably you might end up with a few different namespaces you want to test from which would mean more remote callers in different namespaces, which got me thinking that there might be a more generic approach.

The code below, essentially creates these wrapper classes for you by compiling them on the fly and then invoking them.

class CodeMaker {
    static string _codesectionOne = @"
        using Foo.Common.WebService;
        namespace ";
    static string _codesectionTwo = @" {
            class RemoteCaller {
                static public string Run() {
                    return CallerStuff.FindCallingNameSpace();
                }
            }
        }";

    public static string CompileAndCall(string targetNamespace, 
                                        string referenceAssembly) {
        CompilerParameters CompilerParams = new CompilerParameters();
        string outputDirectory = Directory.GetCurrentDirectory();

        CompilerParams.GenerateInMemory = true;
        CompilerParams.TreatWarningsAsErrors = false;
        CompilerParams.GenerateExecutable = false;
        CompilerParams.CompilerOptions = "/optimize";

        string[] references = { "System.dll", referenceAssembly};
        CompilerParams.ReferencedAssemblies.AddRange(references);

        CSharpCodeProvider provider = new CSharpCodeProvider();
        var codeToCompile = _codesectionOne + targetNamespace + _codesectionTwo;

        CompilerResults compile = provider.CompileAssemblyFromSource(CompilerParams, 
                                                                     codeToCompile);

        if (compile.Errors.HasErrors) {
            string text = "Compile error: ";
            foreach (CompilerError ce in compile.Errors) {
                text += "rn" + ce.ToString();
            }
            throw new Exception(text);
        }

        Module module = compile.CompiledAssembly.GetModules()[0];
        Type mt = null;
        MethodInfo methInfo = null;

        if (module != null) {
            mt = module.GetType(targetNamespace + ".RemoteCaller");
        }

        if (mt != null) {
            methInfo = mt.GetMethod("Run");
        }

        if (methInfo != null) {
            return (string)methInfo.Invoke(null, null);
        }
        throw new InvalidOperationException("It's all gone wrong!");
    }
}

You would then invoke the method from your test:

Assert.AreEqual("Fiddle", CodeMaker.CompileAndCall("Wibble.Fiddle.Con", "SO.dll"));
Assert.AreEqual("Fuddle", CodeMaker.CompileAndCall("Wibble.Fuddle.Con", "SO.dll"));

Note, "SO.dll" in the example above is the name of the assembly containing the CallerStuff.FindCallingNamespace

Using the compiler to generate the caller classes is probably overkill for what you're after and you might have to tweak the error handling in the code if you do decide to use it. If you're invoking the generated classes multiple times from different tests, then it may also be worth caching them, possibly through a dictionary keyed off namespace rather than compiling them every time. Compile + Call code is based on this blog post by Simeon Pilgrim.