4

We use Specflow and currently, most of our assertions are done using Fluent Assertions, ie look something like:
myval.Should().NotBe(null)

All is working well, however there are some cases in which we want the same code to sometimes Assert Inconclusive, and sometimes not.

For example, let us assume we have a step which calls a method to install the AUT.
The test which asserts that the AUT can be installed correctly will use this method as part of a WHEN/THEN step.
In this instance, if installation fails, we want to proceed as normal and fail the test.
GIVEN The AUT is NOT installed WHEN I install the AUT

However, for all other tests, the same method may be called as part of a GIVEN step or as part of a BeforeScenario / BeforeFeature hook, and in that instance, if the installation fails, the test should be failed as inconclusive, because it was not the test itself that failed, but the Setup stage. GIVEN the AUT is installed WHEN I perform function X of the AUT

So in this example, let us assume that the step definitions for WHEN I install the AUT in the first test, and GIVEN the AUT is installed in the second test simply call a helper method with something like AppFacade.Install()

Of course we can litter AppFacade.Install() and all our other helper methods with conditionals etc, but I was wondering if anyone out there has a more elegant solution - ie way to extend an assertion engine so that it automatically applies this logic - I see no barriers on the Specflow side to achieving this insofar as you can examine the Specflow context objects to detect what kind of step you are on.
Ideally we would like to stick with Fluent Assertions, but if this overly complicates things, we would be willing to reconsider.

Clive Galway
  • 482
  • 4
  • 13

3 Answers3

5

I was going to suggest you were not asking the right question, since you are implying that when a Specflow step execution fails the test should be inconclusive, but you already realized it.

So to answer the actual question you have:

Is it possible to throw an exception which can mark the test as inconclusive given the above logic?

Yes, you can use the after AfterStep hook (see Specflow hooks here) to check the is there was any exception thrown and execute Assert.Inconclusive() if you're using Nunit or any equivalent method, like Specflow's ScenarioContext.Current.Pending() to not be attached to the testing framework installed, the only caveat is that ScenarioContext.Current.Pending() does not clear ScenarioContext.TestStatus, so your test runner will mark it as red and you'll have to use reflection to set the TestStatus property as described here.

It would look something like this:

[AfterStep]
public void Check()
{
    if (ScenarioContext.Current.TestError is AssertionException &&
        ScenarioContext.Current.CurrentScenarioBlock == ScenarioBlock.Given)
        Assert.Inconclusive(ScenarioContext.Current.TestError.Message);
}

Edit

Here is a demo I created.

Community
  • 1
  • 1
Maximo Dominguez
  • 2,075
  • 18
  • 35
  • 1
    Nice idea for a practical solution – Sam Holder Apr 01 '17 at 07:00
  • 1
    Gonna give this a try, and if it meets my needs, will mark as answer. Thankyou! – Clive Galway Apr 03 '17 at 11:13
  • @MaximoDominguez - Really sorry for taking my time on this one, been super busy. Thanks so much for making the sample SLN, but unfortunately I cannot get it to run. Upon opening the SLN, I need to edit the binding regex for it to even tie the steps to the code, and even after I do that, VS 2015 finds no tests in the SLN - nothing appears in the Test Explorer, and right-clicking the feature file and selecting "Debug Scenarios" does nothing. Sigh, Specflow is so super flaky it's unreal. I literally have 10-20 Specflow crashes a day. – Clive Galway May 17 '17 at 16:58
  • You shouldn't have to modify anything, the only thing you might be missing is visual studio's [nunit test adapter](https://marketplace.visualstudio.com/items?itemName=NUnitDevelopers.NUnit3TestAdapter) – Maximo Dominguez May 17 '17 at 17:08
1

Seems to me that you have different requirements for the step when it is a given vs a when or then. Specflow already has something built in to handle this, the fact that the engine will call the method tagged as given for given steps and not for when and then steps.

It feels to me like the simplest solution is to not use the same code for the step when its a given and a when, but have dedicated code for the given step which does what you want differently to the when and then steps.

I'm not sure that you are going to be able to make it bend to your desires and be able to reuse the same steps

Sam Holder
  • 32,535
  • 13
  • 101
  • 181
  • Ideally, our step definition files contain no logic. We use a layered approach, so in the example of the installing of the AUT, all the step definition would contain is a call to the Facade of the AUT – Clive Galway Mar 31 '17 at 20:05
  • Why would the façade of the application under test have assertions? Surely the point of your steps is to contain the setup, actions and assertions you want to perform? – Sam Holder Mar 31 '17 at 20:30
  • OK, food for thought here - maybe I am not asking for the right thing. At the moment, for example, in the code in the helper method which tries to install the AUT would throw an exception if the file did not exist, which will always fail the test. Is it possible to throw an exception which can mark the test as inconclusive given the above logic? Maybe a custom exception handler which checks if you are in Given step and if so uses Assert.Inconclusive instead of throwing exception? – Clive Galway Mar 31 '17 at 22:24
  • In that situation, in my given step, i would try catch around the install and fail inconclusive in the catch. In the when/then step i would actually fail. But it still comes back to the step containing the logic that you need it to have. – Sam Holder Mar 31 '17 at 22:27
  • Yes, this seems to be the direction I should shift my investigation to, thanks Sam. When you say "it still comes back to the step containing the logic that you need it to have", do you mean best practice says that your assertion should only be in the step definition file, not buried in some helper class? – Clive Galway Mar 31 '17 at 22:48
  • I don't know about best practice, all i can give is one person's view from having spent 3 years building 2500+ scenarios. We usually have the assertions in the steps, or sometimes in a context class. It seems in your case the important thing is not to bundle the functionality up into pieces that are too big to reuse appropriately. In the end you often want to do different things in the given version of the step than in the then version of it. Don't fight that, build your infrastructure to enable that. – Sam Holder Apr 01 '17 at 06:57
1

Not sure if this will help but you can inherit the TechTalk.SpecFlow.Steps class on your steps class like so;

public sealed class MyFeatureSteps : Steps

What does this allow you to do? Well you can do something like this;

[Given(@"Everything is installed")]
public void GivenEverythingIsInstalled()
{
  // Perform Installation with no assertion
  // You can use this step just to do the setup
  // No assertions literally just perform all the actions required to install 
}

[Then(@"its all installed correctly")]
public void ThenItsAllInstalledCorrectly()
{
  // Because you have inherited the TechTalk.SpecFlow.Steps class
  Given("Everything is installed");
  Assert.IsTrue(CorrectInstallCheckMehtod(), "It should have installed");
}

Basically you can make steps as granular as you want now and just put assertions where you need them, and you can reference other steps within each other.

Take note while you can reference a GIVEN step within a WHEN (or THEN) step, if the last thing you do within the step is call another and it is different to its parent I.e a WHEN step references a GIVEN step if you try to use an AND in your feature file its going to be a GIVEN AND. I hope that makes sense

I hope this will help you. I think you should avoid not failing tests even if that's not what you are specifically testing because it still highlights some broken functionality. Alternatively put an assert after the install that checks its installed this will cause the test to fail before it even starts, again this really is not a bad thing.

Sirk
  • 1,547
  • 2
  • 12
  • 18
  • "I think you should avoid not failing tests even if that's not what you are specifically testing because it still highlights some broken functionality." So if I have two tests: 1) Can I install Calculator? 2) Can the calculator add 1 and 1? If (2) fails because it cannot install calculator, then failing the test wrecks your metrics. You have no problem Adding 1 and 1, you have a problem installing the AUT. By failing the test as inconclusive, your stats for the reliability of the code covered by test (2) are not impacted. – Clive Galway Mar 31 '17 at 12:18
  • Furthermore, the example would have one of the Given steps as "Given the AUT is NOT installed". It would then be followed by a step "WHEN I install the AUT". The other test would be "Given that the AUT is installed", "When I perform function x of the AUT". The step definition for "Given the AUT is installed" and "When the AUT is installed" would both solely contain something like AppFacade.Install(). So what I am saying is I would like ALL the Asserts in AppFacade.Install() to automatically detect the context and fail/inconclusive as appropriate. – Clive Galway Mar 31 '17 at 20:13