Skip to content
This repository has been archived by the owner on Jun 12, 2021. It is now read-only.

Re-using steps does not work #361

Closed
epitka opened this issue Dec 21, 2016 · 6 comments
Closed

Re-using steps does not work #361

epitka opened this issue Dec 21, 2016 · 6 comments
Labels
question Further information is requested

Comments

@epitka
Copy link

epitka commented Dec 21, 2016

I expected this to work, but it does not as steps do not execute in sequential order. In this case "When" part would be executed first. But wiki page "How can I reuse steps?" suggests that it is approach that would work.

using System;
using Xunit;
using Xbehave;
using XBehaveQuickStart;

namespace SR.Domain.Policy.Integration.Tests.Handlers
{
    public class CalculatorFeature
    {
        private Calculator calculator;
        private int answer;
        private int x;
        private int y;

        [Background]
        public void Background()
        {
            "Given a calculator"
                .f(() =>
                {
                    this.calculator = new Calculator();
                });
        }

        [Scenario]
        public void Addition()
        {
           
            "Given the number 1"
                .f(() => x = 1);

            "And the number 2"
                .f(() => y = 2);
            
            When.Add(calculator, x,y, answer);

            Then.DoAsserts(answer);
        }

        public static class When
        {
            public static void Add(Calculator calculator, int x, int y, int answer)
            {
                "When I add the numbers together"
               .f(() => answer = calculator.Add(x, y));
            }
        }

        public static class Then
        {
            public static void DoAsserts(int answer)
            {
                "Then the answer is 3"
                .x(() =>  Assert.Equal(3, answer));
            }
        }
    }
}

@adamralph
Copy link
Owner

@epitka the steps are executed in the correct order.

The important thing to consider is that when the scenario method is executed, the steps are not executed. They are collected together and executed after the method has exited. For this reason it is important to never execute any code outside the step expressions.

In the code you posted, you are calling When.Add(calculator, x,y, answer);. At the time that statement is executed, the background step which assigns calculator has not yet been executed, so calculator is null. In the When.Add method, the step lambda closes over the calculator parameter, which is null at the time that method is executed, and hence the NullReferenceException is thrown when that expression is executed.

Another problem is that you are assigning the answer parameter inside When.Add but this value will never be seen outside that method since the argument is passed by value and not by reference. This makes it awkward to re-use your steps in the way you are intending, but it is normal C# behaviour.

I would suggest a different approach: I would aim to move the reusable parts of your testing code into an internal DSL, e.g.

[Scenario]
public void Addition(int x, int y, Calculator calculator, int answer)
{
    "Given the number 1"
        .x(() => x = Numbers.GetOne());

    "And the number 2"
        .x(() => y = Numbers.GetOne());

    "And a calculator"
        .x(() => calculator = Calculators.Create());

    "When I add the numbers together"
        .x(() => answer = Calculators.Add(calculator, x, y));

    "Then the answer is 3"
        .x(() => Numbers.IsThree(answer));
}

Where Numbers and Calculators are static types in the testing DSL. Of course, this looks very contrived in the context of the calculator quickstart sample, but it can be very powerful and effective in the context of a less trivial domain.

@adamralph
Copy link
Owner

@epitka and btw, thanks for raising this. 👍

@adamralph adamralph added the question Further information is requested label Dec 22, 2016
@epitka
Copy link
Author

epitka commented Dec 23, 2016

Thanks for thorough explanation, but then the blog entry should be checked as it specifically states that this would work. Or I am not understanding this code?

public class UltraCoolFeature
{
    [Scenario]
    public void BeingUltraCool(Foo fixture, Bar sut, Baz result)
    {
        "Given..."
            .f(() => ...);
        "When..."
            .f(() => ...);
        Then.CoolOutcomes(result);
    }
}

@adamralph
Copy link
Owner

@epitka you are correct, that code will not work. I guess I must not have tested that code before writing that wiki page, which is rather embarassing. 😒

I'll take down that content and replace it later with something that actually works. Thanks for bringing this to my attention! 👍

@adamralph
Copy link
Owner

Actually, in my defense, I think that code may have worked with an earlier version of xbehave (1.x) which IIRC, did execute steps eagerly, as they are defined, rather than collecting them and executing them later.

This is actually an interesting consideration. It may be better to investigate reverting back to that style of eager execution. I'll raise a separate issue for that.

@adamralph
Copy link
Owner

@epitka I guess this discussion has drawn to a close. Thanks again for raising this, please subscribe to #362 if you'd like to track the progress of that issue.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants