Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does the Shouldly assertion library know the expression the assertion was applied to?

Tags:

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?

like image 545
Stilgar Avatar asked Oct 10 '16 13:10

Stilgar


People also ask

Why use Shouldly?

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.

What is Shoudly?

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.


2 Answers

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:

  • Use the StackTrace to retrieve the location of the source code and the line of the statement
  • Parse the source code to retrieve the exact statement

Of course, it can work only if you're running it on the same machine you used to compile the code.

like image 187
Kevin Gosse Avatar answered Oct 10 '22 02:10

Kevin Gosse


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.

like image 38
Ant P Avatar answered Oct 10 '22 01:10

Ant P