Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I make specflow cope nicely with dates/times?

I wish to be able to write tests like this:

Background:
  Given a user signs up for a 30 day account

Scenario: access before expiry
  When they login in 29 days
  Then they will be let in

Scenario: access after expiry
  When they login in 31 days
  Then they will be asked to renew

Scenario: access after acounnt deleted
  When they login in 2 years time
  Then they will be asked to register for a new account

How do I do the specflow side of the tests?

Edit: how can the same step definitions cope with both "31 days" and "2 years time"

like image 751
Ian Ringrose Avatar asked Mar 03 '11 22:03

Ian Ringrose


1 Answers

Building this .feature file will create a code behind for the tests. You then need to wire up each step to a method. The easiest way to do this is,

1: debug the tests, the test will fail as inconclusive. Looking at the Test run results specflow helps you by adding a template for this test. the error message will look something like this

Assert.Inconclusive failed. No matching step definition found for one or more steps.

    [Binding]
public class StepDefinition1
{
    [Given(@"a user signs up for a 30 day account")]
    public void GivenAUserSignsUpForA30DayAccount()
    {
    }

    [When(@"they login in 29 days")]
    public void WhenTheyLoginIn29Days()
    {
        ScenarioContext.Current.Pending();
    }

    [Then(@"they will be let in")]
    public void ThenTheyWillBeLetIn()
    {
        ScenarioContext.Current.Pending();
    }
}

2: Copy this into a new specflow step definition file, which is basically just unit test class populated with specflow attributes. Now there are some tricks you can do to help you out. in the GivenAUserSignsUpForA30DayAccount method i would create a user that will be used in the test that has a 30 day trial account. A private member would work fine here so you can access them between methods, but this only works if all the methods are in the same class. if you try to reuse methods between multiple features/classes you will need to look into saving your object into the ScenarioContext

3: When the specflow test runs it looks for a method that has an matching attribute with the same string. A trick here is that you can u pass parameters to the method using wild cards in the Method Attribute. There are 2 different file cards

(.*) means you are passing a string to that method (\d+) means you are passing an int to that method.

Because your When method is common you could reuse it with parameters like this.

    [When(@"they login in (\d+) days")]
    public void WhenTheyLoginInDays(int daysRemaining)
    {
        Account.DaysRemaining = daysRemaining;
    }

4: finally add your Asserts into the Then method so the end result looks something like this. (note that personally i would restructure the wording of the feature a bit and pass it expected results that way the test logic isn't as nasty as my example, look at scenario outlines for data driven tests)

    [Binding]
public class StepDefinition1
{
    UserAccount user;

    [Given(@"a user signs up for a 30 day account")]
    public void GivenAUserSignsUpForA30DayAccount()
    {
        user = AccountController.CreateNewUser("bob", "password", AccountType.Trial);
    }

    [When(@"they login in (\d+) days")]
    public void WhenTheyLoginInDays(int daysRemaining)
    {
        Account.DaysRemaining = daysRemaining;
    }

    [Then(@"they will (.*)")]
    public void ThenTheyWillBeLetIn(string expected)
    {
        //check to see which test we are doing and then assert to see the expected result.
        if(string.Compare(expected, "be let in", true)
            Assert.AreEqual(LoginResult.Passed, LoginService.Login);
        if(string.Compare(expected, "be asked to renew", true)
            Assert.AreEqual(LoginResult.Passed, LoginService.Login);

    }
}
like image 93
Nitro52 Avatar answered Oct 04 '22 11:10

Nitro52