The Shouldly assertion library for .NET somehow knows what expression the assertion method was called on so it is able to display it into the message. I tried to find out how it works but got lost in the source code. I suspect it looks into the compiled code but I would really like to see how this happens. From the documentation
map.IndexOfValue("boo").ShouldBe(2); // -> map.IndexOfValue("boo") should be 2 but was 1
Somehow Shouldly knows the expression map.IndexOfValue("boo") and was able to display it in the test failure message. Does anyone know how this happens?
One of the benefits of Shouldly is that it can help to improve the readability of test code. It does this in two ways: Disambiguates expected and actual values. Produces fluently readable code.
Shouldly is an assertion framework which focuses on giving great error messages when the assertion fails while being simple and terse. This is the old Assert way: Assert. That(contestant. Points, Is.
Looking at the code, that's pretty smart.
The magic is happening in the ActualCodeTextGetter
class. First, it retrieves the line of the source code file by using the StackTrace:
StackTrace stackTrace = trace ?? new StackTrace(true);
// Cut for brevity
StackFrame stackFrame = frame;
this.ShouldlyFrameIndex = index - 1;
string fileName = stackFrame.GetFileName();
this._determinedOriginatingFrame = fileName != null && File.Exists(fileName);
this._shouldMethod = this.ShouldlyFrame.GetMethod().Name;
this.FileName = fileName;
this.LineNumber = stackFrame.GetFileLineNumber() - 1;
Once it has the name of the source code file, along with the line and offset of the statement, it's just a matter of reading directly the file:
private string GetCodePart()
{
string str = "Shouldly uses your source code to generate its great error messages, build your test project with full debug information to get better error messages\nThe provided expression";
if (this._determinedOriginatingFrame)
{
string codeLines = string.Join("\n", ((IEnumerable<string>) File.ReadAllLines(this.FileName)).Skip<string>(this.LineNumber).ToArray<string>());
int indexOfMethod = codeLines.IndexOf(this._shouldMethod);
if (indexOfMethod > 0)
str = codeLines.Substring(0, indexOfMethod - 1).Trim();
str = !str.EndsWith("Should") ? str.RemoveVariableAssignment().RemoveBlock() : this.GetCodePartFromParameter(indexOfMethod, codeLines, str);
}
return str;
}
There's a lot more logic going on to isolate precisely the statement, but in short the trick is:
Of course, it can work only if you're running it on the same machine you used to compile the code.
It does it by parsing the stack trace for the current call stack until it finds a method that is not internal to Shouldly and treats its name as the name of the method under test.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With