Tampering SharePoint assemblies part 2
In the previous article, I explained the context of a legacy SharePoint code which should be tested. For instance, regression testing should cover methods such as:
public string DoSomething(int whatever)
{
var urls = SPContext.Current.Site.WebApplication.AlternateUrls;
// Do something with those URIs.
}
For those who are unfamiliar with SharePoint, in order to be tested, the method requires access to a SharePoint farm, with the prerequisite of installing and deploying the code. As you can guess, that's not really the spirit of unit testing.
Now, what if we could run this method from unit tests by simply doing:
SPWebApplicationExchanger.Instance.AlternateUrls.RemoveAll(c => true);
SPWebApplicationExchanger.Instance.AlternateUrls.Add(
new SPAlternateUrl("http://demo.example.com/", SPUrlZone.Default));
var actual = new Demo().DoSomething(5);
var expected = "Expected result goes here";
Assert.AreEqual(expected, actual);
This is now—or will be soon—possible.
Previously, I explained that existent frameworks related to proxies and AOP can't help in a context of such tests, but tampering could be a solution. The prototype shows that it is indeed technically possible to unit test such code.
The previous article suggested to tamper the assemblies on the fly. A few attempts have shown that this approach is a failure for two reasons:
Tampering even a small Hello World assembly takes time, and it's out of question to use this approach for large assemblies such as
Microsoft.SharePoint
.More importantly, there is no way, aside WCF, you can exchange information between the test and the underlying code. For example, if you specify the URI of the
SPSite
in the test, and then the underlying code is doingSPContext.Current.Site.Url
, you'll get anull
. I'm not sure what is causing this, since even static fields or properties appear to be different when accessed from tests then in a context of the underlying code, but the fact is that exchanging information becomes painful, and a few scenarios become impossible. One of such scenarios is when you want to specify, in test, an action which will then be executed from the underlying code.
The prototype, on the other hand, uses a different approach. Its build task is called when the test project is about to be built. From there, it matches the proxy assemblies to the original ones, combines them, and tampers the specific methods. As a result, a tampered method can still call classes from the proxy assembly, and can use a data exchanger to actually communicate with tests.
For the assemblies in GAC, an additional step was required, since CLR ignores local assemblies if they exist in GAC. Hopefully, bindingRedirect
directive made it possible to circumvent this mechanism by changing the version of the tampered assembly.
The nice thing about this is that the same technique can be applied for other assemblies as well, including .NET Framework itself. This makes it possible to test code which, for instance, relies on System.IO.File
by calling methods such as File.WriteAllText
while bypassing access to the file system.
This may be the ultimate solution for regression testing of code written by inexperienced programmers.