3
votes

Here is question about context injection in specflow. I have class with my entity:

public class OrderInfo
{
    public string Status { get; set; }
    public string OrderName { get; set; }
}

In another class I'm using this entity to create new order:

[Binding]
public class OrderSteps
{
    private NgWebDriver driver;
    private OrderInfo orderInfo = new OrderInfo();

    public OrderSteps(NgWebDriver driver,OrderInfo orderInfo)
    {
        this.orderInfo = orderInfo;
        this.driver = driver;
    }

    [When(@"I am creating new order")]
    public void WhenIAmCreatingNewOrder(Table table)
    {
        var orderListPage = new OrdersListPage(driver);
        orderInfo = orderListPage.CreateNewOrder(table.CreateInstance<OrderInfo>());
    }
}

In this class my method returns some data that is stored in 'orderInfo' object. Everything is okay till I needed to use data from that object in another class. I'm trying to get my object data, but it's empty inside....

    [Binding]
public class BuyerPortalSteps
{
    private NgWebDriver driver;
    private OrderInfo orderInfo;

    public BuyerPortalSteps(NgWebDriver driver, OrderInfo orderInfo)
    {
        this.driver = driver;
        this.orderInfo = orderInfo;
    }
}

So the question is: how to use data in the second class from the object that has been filled with data of the first class

2

2 Answers

0
votes

Seems like I found a solution. I have made a child subclass for my OrderInfo:

public class OrderInfoContext
{
    public OrderInfo OrderInfo { get; set; }
}

And there are some changes in my Binding class:

public class OrderSteps
{
    private NgWebDriver driver;
    private List<FilterInfo> filterData = new List<FilterInfo>();
    private OrderInfoContext orderInfoContext;
    private OrderInfo orderInfo;

    public OrderSteps(NgWebDriver driver, OrderInfoContext orderInfoContext)
    {
        this.driver = driver;
        this.orderInfoContext = orderInfoContext;
    }

    [When(@"I am creating new order")]
    public void WhenIAmCreatingNewOrder(Table table)
    {
        var orderListPage = new OrdersListPage(driver);
        orderInfo = orderListPage.CreateNewOrder(table.CreateInstance<OrderInfo>());
        orderInfoContext.OrderInfo = orderInfo;
    }
}

And now I can use stored info in my other class:

    [Binding]
public class BuyerPortalSteps
{
    private NgWebDriver driver;
    private OrderInfoContext orderInfoContext;

    public BuyerPortalSteps(NgWebDriver driver, OrderInfoContext orderInfoContext)
    {
        this.driver = driver;
        this.orderInfoContext = orderInfoContext;
    }

    [Then(@"I am looking for order on buyer statistics grid")]
    public void ThenIAmLookingForOrderOnBuyerStatisticsGrid()
    {
        BuyerPortalDashboard buyerPortalDashboard = new BuyerPortalDashboard(driver);
        buyerPortalDashboard.CheckBuyerDashboardStatistic(orderInfoContext.OrderInfo);
    }
}

I think that the reason was that if you want to share data between classes you have to initialize this data once and never try to edit it again, because it can be changed on anywhere on the way before you will read it....this is just my assumption

0
votes

You can use the ScenarioContext and pass that around. The nice thing about this approach is you don't need to register anything new with the object container.

To create the "current order" in OrderSteps:

[Binding]
public class OrderSteps
{
    private readonly NgWebDriver driver;
    private readonly ScenarioContext scenario;

    private OrderInfo CurrentOrder
    {
        get => (OrderInfo)scenario["OrderInfo"];
        set => scenario["OrderInfo"] = value;
    }

    public OrderSteps(NgWebDriver driver, ScenarioContext scenario)
    {
        this.driver = driver;
        this.scenario = scenario;
    }

    [When(@"I am creating new order")]
    public void WhenIAmCreatingNewOrder(Table table)
    {
        var orderListPage = new OrdersListPage(driver);

        CurrentOrder = orderListPage.CreateNewOrder(table.CreateInstance<OrderInfo>());;
    }
}

Then to use the OrderInfo object in another class:

[Binding]
public class BuyerPortalSteps
{
    private readonly NgWebDriver driver;
    private readonly ScenarioContext scenario;
    private OrderInfo CurrentOrder => (OrderInfo)scenario["OrderInfo"];

    public BuyerPortalSteps(NgWebDriver driver, ScenarioContext scenario)
    {
        this.driver = driver;
        this.scenario = scenario;
    }

    [Then(@"I am looking for order on buyer statistics grid")]
    public void ThenIAmLookingForOrderOnBuyerStatisticsGrid()
    {
        var buyerPortalDashboard = new BuyerPortalDashboard(driver);

        buyerPortalDashboard.CheckBuyerDashboardStatistic(CurrentOrder);
    }
}

I've also created extension methods on ScenarioContext, which can be handy as well:

namespace YourProject.Tests
{
    public static class OrderScenarioContextExtensions
    {
        public static OrderInfo GetCurrentOrder(this ScenarioContext scenario)
        {
            return (OrderInfo)scenario["OrderInfo"];
        }

        public static void SetCurrentOrder(this ScenarioContext scenario, OrderInfo currentOrder)
        {
            scenario["OrderInfo"] = currentOrder;
        }
    }
}

Then you can call these extension methods wherever you have a ScenarioContext object:

var order = scenario.GetCurrentOrder();

var order = new OrderInfo();

scenario.SetCurrentOrder(order);

More information: Scenario context injection (SpecFlow docs)