-
Notifications
You must be signed in to change notification settings - Fork 8
Background
I've often had the development experience of having to switch back and forth between Javascript / Typescript development and .NET development (specifically, C#, but the language shouldn't matter that much).
Typically, in Javascript, I'm either using Jasmine or "the trinity" - Mocha, Sinon, Chai - and typically, that means that I'm writing tests like:
(in Jasmine)
expect(foo).toEqual(bar);
expect(moo).not.toEqual(cow);
(with Chai)
expect(foo).to.equal(bar);
expect(moo).not.to.equal(cow);
Typically, in C#, I'm using NUnit, so my assertions would have been more like:
Assert.AreEqual(foo, bar);
// or, the "newer syntax":
Assert.That(foo, Is.EqualTo(bar));
// or, with AssertionHelper:
Expect(foo, Is.EqualTo(bar));
The last syntax was what I settled on in 2016, but in early 2017, AssertionHelper was deprecated - the NUnit team felt that the cost of maintaining it wasn't worth the value to the user-base. They may be right.
Quickly, I whipped up NUnit.StaticExpect (https://www.nuget.org/packages/nunit.staticexpect), which filled the gap. Since then, there have been some good contributions from others to that project, and if you were happy with AssertionHelper or simply have a lot of tests already using AssertionHelper and would like to get rid of Obsolete warnings before AssertionHelper is officially retired, I recommend using NUnit.StaticExpect. I will continue to maintain it, even though my personal preference has shifted to using NExpect.
Whilst working on NUnit.StaticExpect and paying closer attention to how I was using it, some points started to bother me:
"Expect" is future-tense, yet all of the constraints are present-tense ("Is.Empty", "Does.Contain", etc). The future-tense aspect made sense to me: if you write your tests first, you expect a result, some time in the future, which you will code into existence. I also missed the more "dotty" syntax of chai, even though I was using Jasmine for the Typescript testing portion of my code - often a matcher with long camel-cased name becomes more difficult to read. However, I admired how easy it was to extend Jasmine, to have my own assertions available, readable, as part of the library.
I'd still have to make a mindshift when moving between C# and (Java|Type)Script because in one, closing parentheses meant the end of the statement whilst in the other, they meant the beginning of the constraint. I'd always trip up on my initial move from one environment to the other.
I really enjoy how easy it is to extend Jasmine, to add your own assertions as if they were part of the core library. It meant that you could write expressive tests - instead of a bunch of "assert {x} is equal to {y}" logic, you could bunch logic pertaining to a particular concept into a matcher which could be re-used. For example, the first logical test when creating a new Polymer element is to prove that it will actually render as a Polymer element. At the time, I was using polymer-ts, and there were about 7 things you had to get right before your element would even render -- and it was super-easy to forget one. So I had a matcher which, when it failed, would list all possible reasons, and it had nice readable syntax: expect(sut).toBeAPolymerComponent('component-tag');
. Once that test passed, I knew I could use the component-tag
element in my production code.
And so, on a bit of a lark (partly because I wanted to see what it would take to do it), NExpect was born, with syntax inspired by Chai and extensibility inspired by Jasmine.
On to Syntax basics