6
\$\begingroup\$

Background

I already changed my mind towards the SOLID principles, and am applying them in everything that I create.

Now I am reading a lot of articles about TDD and BDD, aiming to begin applying those concepts in new projects. I decided to use xUnit and Moq as my helpers, because of simplicity and LINQ to Mocks.

Like I try to do with every new concept that I learn, I am trying to come up with a "pattern" to follow, so things become more familiar to work with.

The code

The following code is my first attempt to get into it (mainly based on two articles in the comments). The class being tested is not currently implemented, the point here is to came with a good approach to implement BDD using xUnit with the visual studio runner.

To get at that, I tried to use inheritance to pass context to each test case of the same SUT, using names that give a clear intent of each case. Also I tried to separate the action from the assertion and context setup.

The intent of the future implemented class is to generate specific flavors of some IQuery (like IFrom, ICount, IName), which represents pieces of an SQL query, dynamically build in some other class, beyond the scope of this.

The IInstructionFactory dependency is the abstraction of a class that generate those IQuery derived interfaces and is implemented in another assembly (one for MySql, another for SQL Server).

The IExpressionTranslator dependency is the abstraction of a class that gets some lambda expression and converts to ICondition (which in turn is a derivation of IQuery too).

Base context class:

public abstract class ContextSpecification {
    protected ContextSpecification() {
        Context();
        BecauseOf();
    }

    protected virtual void BecauseOf() {
    }
    protected virtual void Context() {
    }
    protected virtual void Cleanup() {
    }
}

My first try to BDD unit testing:

public class describe_CommonQueryCreator : ContextSpecification {
    private CommonQueryCreator queryCreator;
    private Mock<IInstructionFactory> factoryMock;
    private Mock<IExpressionTranslator> translatorMock;

    protected override void Context() {
        SetupExpressionTranslator();
        SetupInstructionFactory();
        queryCreator = new CommonQueryCreator(factoryMock.Object, translatorMock.Object);
    }

    protected virtual void SetupExpressionTranslator() => translatorMock = new Mock<IExpressionTranslator>();
    protected virtual void SetupInstructionFactory() => factoryMock = new Mock<IInstructionFactory>();

    public class when_creating_count_query : describe_CommonQueryCreator {
        private ICount resultQuery;

        public class given_correct_input : when_creating_count_query {
            private readonly string tableName = "MimasTest";
            private readonly Expression<Func<bool>> predicate = () => true;
            private Views.ICondition mockedCondition;

            protected override void SetupExpressionTranslator() {
                base.SetupExpressionTranslator();
                mockedCondition = new Mock<Views.ICondition>().Object;
                translatorMock.Setup(translator => translator.TranslateToCondition(predicate)).Returns(mockedCondition);
            }
            protected override void SetupInstructionFactory() {
                base.SetupInstructionFactory();
                IName mockedName = new Mock<IName>().Object;
                INameList mockedTables = new Mock<INameList>().Object;
                IFrom mockedFrom = new Mock<IFrom>().Object;
                factoryMock.Setup(factory => factory.CreateName(tableName)).Returns(mockedName);
                factoryMock.Setup(factory => factory.CreateNameList(It.Is<IEnumerable<IName>>(valueList => valueList != null && valueList.Count() == 1 && valueList.First() == mockedName))).Returns(mockedTables);
                factoryMock.Setup(factory => factory.CreateFrom(mockedTables, It.Is<IEnumerable<IJoin>>(joinList => joinList != null && !joinList.Any()))).Returns(mockedFrom);
                factoryMock.Setup(factory => factory.CreateCount(mockedFrom, It.Is<IEnumerable<ICondition>>(conditionList => conditionList != null && conditionList.Count() == 1 && conditionList.First() == mockedCondition)));
            }

            protected override void BecauseOf() => resultQuery = queryCreator.CreateCountQuery(tableName, predicate);

            [Fact]
            public void it_should_return_not_null() => Assert.NotNull(resultQuery);
            [Fact]
            public void it_should_call_InstructionFactory_CreateCount_with_correct_values() => factoryMock.VerifyAll();
        }
        public class given_null_tableName_argument : when_creating_count_query {
            [Fact]
            public void it_should_throw_ArgumentNullException() => Assert.Throws<ArgumentNullException>("tableName", () => queryCreator.CreateCountQuery(null, () => true));
        }
        public class given_empty_tableName_argument : when_creating_count_query {
            [Fact]
            public void it_should_throw_ArgumentException() => Assert.Throws<ArgumentException>("tableName", () => queryCreator.CreateCountQuery(string.Empty, () => true));
        }
        public class given_null_predicate_argument : when_creating_count_query {
            [Fact]
            public void it_should_throw_ArgumentNullException() => Assert.Throws<ArgumentNullException>("predicate", () => queryCreator.CreateCountQuery("MimasTest", null));
        }
    }
}

My future-implemented SUT class:

internal sealed class CommonQueryCreator {
    public CommonQueryCreator(IInstructionFactory instructionFactory, IExpressionTranslator expressionTranslator) {
        InstructionFactory = instructionFactory;
        ExpressionTranslator = expressionTranslator;
    }

    public IInstructionFactory InstructionFactory {
        get;
    }
    public IExpressionTranslator ExpressionTranslator {
        get;
    }

    public ICount CreateCountQuery(string tableName, Expression<Func<bool>> predicate) {
        throw new NotImplementedException();
    }
}

This is how it looks like in Test Explorer: Test Explorer

I think my Moq expectation setup is correct, as I see, everything should work correctly.

My questions are:

Is this a right way of doing BDD?

Normally I use pascal casing for method names, but I read (and comprehend the arguments) that underscores are better for reading in BDD name conventions, is this a right way?

In this "pattern", when asserting exceptions I can't use a BecauseOf() override because of it being called in base class constructor. So, I called the SUT function inside the Assert.Throw(), I couldn't become with a workaround, any ideas?

Structurally is something that could be better?

\$\endgroup\$

closed as unclear what you're asking by Graipher, alecxe, Vogel612, Quill, pacmaninbw Jun 21 '17 at 11:45

Please clarify your specific problem or add additional details to highlight exactly what you need. As it's currently written, it’s hard to tell exactly what you're asking. See the How to Ask page for help clarifying this question. If this question can be reworded to fit the rules in the help center, please edit the question.

  • \$\begingroup\$ Articles: this and this \$\endgroup\$ – LoRdPMN Jun 19 '17 at 19:27
3
\$\begingroup\$
public abstract class ContextSpecification {
    protected ContextSpecification() {
        Context();
        BecauseOf();
    }

    protected virtual void BecauseOf() {    }
    protected virtual void Context() {    }
    protected virtual void Cleanup() {    }
}

This class does a few things wrong:

  1. The constructor seems to call methods that go beyond a normal initialization.
  2. The constructor calls virtual methods. This might not work as you expect it: Virtual member call in a constructor

    By having a virtual call in an object's constructor you are introducing the possibility that inheriting objects will execute code before they have been fully initialized.

  3. The three virtual methods have no implementation. They should be abstract.
\$\endgroup\$
  • \$\begingroup\$ As you said, that class smells, it was created that way because xUnit doesn't have a [TestInitialize] attribute, instead it uses the constructor. At the moment I deleted that class and tried to do the context setup and action call in another way, using inheritance without overrides or virtual methods and calling them inside [Fact] methods. The question was put on hold, so I think I can't follow up here, updating with the actual code. I will accept your answer as it leads to a better way. thanks anyway. \$\endgroup\$ – LoRdPMN Jun 21 '17 at 15:26

Not the answer you're looking for? Browse other questions tagged or ask your own question.