Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

F!! Printable scenario - to better support BDD #460

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
Shoplifting
===========
We expect an exception

User: Bob

ShoppingCart:
Articles:

Subtotal:0
Shipping:1
Total:1

Something illegal
-----------------
This action threw an exception: java.lang.RuntimeException: shoplifting

User: Bob

ShoppingCart:
Articles:

Subtotal:0
Shipping:1
Total:1

Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package org.approvaltests.bdd;

import org.approvaltests.Approvals;
import org.approvaltests.strings.Printable;
import org.approvaltests.strings.PrintableScenario;
import org.junit.Test;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

public class PrintableScenarioTest
{
// begin-snippet: bdd_test
@Test
public void printBDDScenario() throws Exception
{
PrintableScenario story = new PrintableScenario("Buy Groceries", "Bob puts two items in the shopping cart");
User currentUser = new User();
ShoppingCart shoppingCart = new ShoppingCart(currentUser);
story.given(new Printable<>(currentUser, UserPrinter::print),
new Printable<>(shoppingCart, ShoppingCartPrinter::print));
story.when("Add oranges to the cart", () -> shoppingCart.add("Oranges", 1, BigDecimal.valueOf(5)));
story.when("Add apples to the cart", () -> shoppingCart.add("Apples", 3, BigDecimal.valueOf(4)));
Approvals.verify(story);
}
// end-snippet
@Test
public void bddScenarioThatThrows() throws Exception
{
PrintableScenario story = new PrintableScenario("Shoplifting", "We expect an exception");
User currentUser = new User();
ShoppingCart shoppingCart = new ShoppingCart(currentUser);
story.given(new Printable<>(currentUser, UserPrinter::print),
new Printable<>(shoppingCart, ShoppingCartPrinter::print));
story.when("Something illegal", () -> {
throw new RuntimeException("shoplifting");
});
Approvals.verify(story);
}
}

class User
{
public String name = "Bob";
}

class Item
{
public String name;
public Integer quantity;
public BigDecimal price;
public BigDecimal amount()
{
return price.multiply(BigDecimal.valueOf(quantity));
}
public Item(String itemName, int quantity, BigDecimal price)
{
this.name = itemName;
this.quantity = quantity;
this.price = price;
}
}

class ShoppingCart
{
private final User user;
public List<Item> articles = new ArrayList<>();
public ShoppingCart(User currentUser)
{
this.user = currentUser;
}
public BigDecimal subtotal()
{
return articles.stream().map(Item::amount).reduce(BigDecimal.ZERO, BigDecimal::add);
}
public BigDecimal shipping()
{
if (Objects.equals(user.name, "Bob"))
{ return BigDecimal.ONE; }
return BigDecimal.ZERO;
}
public BigDecimal total()
{
return this.subtotal().add(this.shipping());
}
public void add(String itemName, int quantity, BigDecimal price)
{
this.articles.add(new Item(itemName, quantity, price));
}
}

class ShoppingCartPrinter
{
public static String print(ShoppingCart shoppingCart)
{
StringBuilder result = new StringBuilder();
result.append("ShoppingCart:\n");
result.append("Articles:\n");
result.append(shoppingCart.articles.stream()
.map(article -> " " + article.name + " price: " + article.price + " quantity: " + article.quantity)
.collect(Collectors.joining("\n")));
result.append("\n");
result.append("Subtotal:" + shoppingCart.subtotal() + "\n");
result.append("Shipping:" + shoppingCart.shipping() + "\n");
result.append("Total:" + shoppingCart.total() + "\n");
return result.toString();
}
}

class UserPrinter
{
public static String print(User user)
{
return "User: " + user.name + "\n";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
Buy Groceries
=============
Bob puts two items in the shopping cart

User: Bob

ShoppingCart:
Articles:

Subtotal:0
Shipping:1
Total:1

Add oranges to the cart
-----------------------
User: Bob

ShoppingCart:
Articles:
Oranges price: 5 quantity: 1
Subtotal:5
Shipping:1
Total:6

Add apples to the cart
----------------------
User: Bob

ShoppingCart:
Articles:
Oranges price: 5 quantity: 1
Apples price: 4 quantity: 3
Subtotal:17
Shipping:1
Total:18

31 changes: 31 additions & 0 deletions approvaltests/docs/how_to/BddScenarios.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<a id="top"></a>

# How to make a BDD Scenario using Approvals
<!-- toc -->
## Contents

* [Introduction](#introduction)
* [Sample Code](#sample-code)<!-- endToc -->

## Introduction
The idea is that you will end up with a descriptive approved file that reads like a BDD Scenario. You can show this to your less-technical collaborators, and they should be able to understand what scenario is being tested without needing to read the test sourcecode.

It doesn't use Gherkin strictly, but you still get a scenario with a name, description, and Given, When, Then steps.

## Sample Code

This test case uses a PrintableScenario:

<!-- snippet: bdd_test -->
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think what you need is the following. These comments in the java file:

// begin-snippet: MySnippetName
public class PrintableScenarioTest
{
  @Test
  public void printBDDScenario() throws Exception
  {
    PrintableScenario story = new PrintableScenario("Buy Groceries", "Bob puts two items in the shopping cart");
    User currentUser = new User();
    ShoppingCart shoppingCart = new ShoppingCart(currentUser);
    story.given(new Printable<>(currentUser, UserPrinter::print),
        new Printable<>(shoppingCart, ShoppingCartPrinter::print));
    story.when("Add oranges to the cart", () -> {
      shoppingCart.add("Oranges", 1, BigDecimal.valueOf(5));
      return null;
    });
    story.when("Add apples to the cart", () -> {
      shoppingCart.add("Apples", 3, BigDecimal.valueOf(4));
      return null;
    });
    Approvals.verify(story.then());
  }
}
// end-snippet

and then you can reference it here in markdown with snippet: MySnippetName


<!-- endSnippet -->

* Construct the scenario at the beginning of the test with a descriptive name and summary.
* When you are done with the code for the "given" step of the test, call story.given() with your list of Printables. Use one printable to wrap each object you have created which you think might change state during the test and that you want to verify.
* In each "When" step of your test, call when() with a descriptive name for what the user is doing. The second argument is a lambda that contains code to actually do the 'when' step. This is optional, you can write it in your test as normal code before the call to 'when' if you prefer.
* The "Then" step is a call to then() which you pass to Approvals.Verify. This will verify the state of all the Printables as they changed throughout the course of the test.

This produces a feature file like this one: [[PrintableScenarioTest.printBDDScenario.approved.txt](../../../approvaltests-tests/src/test/javavorg/approvaltests/bdd/PrintableScenarioTest.printBDDScenario.approved.txt)]

Note - if you don't want to use the BDD terminology you don't have to. You can equally well use 'arrange', 'act', 'print' instead of 'given' 'when' 'then'

Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package org.approvaltests.strings;

import org.lambda.actions.Action0WithException;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;

public class PrintableScenario
{
private final StringBuilder toVerify = new StringBuilder();
private final String name;
private final String description;
private final ArrayList<Printable> printables = new ArrayList<>();
public PrintableScenario(String name, String description)
{
this.name = name;
this.description = description;
}
public PrintableScenario(String name)
{
this(name, "");
}
public void given(Printable... printables)
{
arrange(printables);
}
public void arrange(Printable... printables)
{
this.printables.addAll(Arrays.asList(printables));
toVerify.append(makeHeading(name, description, "="));
toVerify.append(printAll());
}
public String makeHeading(String heading, String summary, String underlineCharacter)
{
int count = heading.length();
// create a string made up of n copies of underlineCharacter
String underlineHeading = String.join("", Collections.nCopies(count, underlineCharacter));
String result = heading + "\n" + underlineHeading + "\n";
if (summary != null && !summary.isEmpty())
{
result += summary + "\n\n";
}
return result;
}
public String printAll()
{
StringBuilder result = new StringBuilder();
for (Printable printable : this.printables)
{
result.append(printable.toString());
result.append("\n");
}
return result.toString();
}
public void when(String action, Action0WithException function)
{
try
{
function.call();
when(action);
}
catch (Throwable e)
{
toVerify.append(makeHeading(action, "This action threw an exception: " + e, "-"));
toVerify.append(printAll());
}
}
public void when(String action)
{
act(action);
}
public void act(String action)
{
toVerify.append(makeHeading(action, "", "-"));
toVerify.append(printAll());
}
public String then()
{
return print();
}
public String print()
{
return toVerify.toString();
}
@Override
public String toString()
{
return print();
}
}
Loading