Given/When/Then JUnit
I was recently introduced to a way to take some of the benefits of using the given/when/then style of specification based testing, and use them directly with JUnit.
You can get some of the benefits of using BDD style testing, but without the dependency on a system such as Cucumber JVM.
The code is on Github.
Let consider the Gherkin from Wikipedia’s page on BDD.
Feature: Returns go to stock
In order to keep track of stock
As a store owner
I want to add items back to stock when they're returned
Scenario: Refunded items should be returned to stock
Given a customer previously bought a black sweater from me
And I currently have three black sweaters left in stock
When he returns the sweater for a refund
Then I should have four black sweaters in stock
Scenario: Replaced items should be returned to stock
Given that a customer buys a blue garment
And I have two blue garments in stock
And three black garments in stock.
When he returns the garment for a replacement in black,
Then I should have three blue garments in stock
And two black garments in stock
We can code this into JUnit test (commit). The class represents the story, and each scenario is a method.
/**
* In order to keep track of stock
* As a store owner
* I want to add items back to stock when they're returned
*/
public class ReturnsGoToStockStoryTest {
@Test
public void refundedItemsShouldBeReturnedToStock() {
// Given a customer previously bought a black sweater from me
// And I currently have three black sweaters left in stock
// When he returns the sweater for a refund
// Then I should have four black sweaters in stock
fail();
}
@Test
public void replacedItemsShouldBeReturnedToStock() {
// Given that a customer buys a blue garment
// And I have two blue garments in stock
// And three black garments in stock.
// When he returns the garment for a replacement in black,
// Then I should have three blue garments in stock
// And two black garments in stock
fail();
}
}
There’s a couple of things to note here:
- None of the comments will appear on display in the CI.
- I’ve added a
fail()at the end of each test to make sure I complete it. Analygous to “pending”. - I’m sticking with Java and JUnit naming conventions.
Now we can implment the first test scenario (commit):
@Test
public void refundedItemsShouldBeReturnedToStock() {
Inventory inventory = new Inventory();
// Given a customer previously bought a black sweater from me
BlackSweater blackSweater = new BlackSweater();
inventory.sellItem(blackSweater, new Customer());
// And I currently have three black sweaters left in stock
inventory.addItem(new BlackSweater());
inventory.addItem(new BlackSweater());
inventory.addItem(new BlackSweater());
// When he returns the sweater for a refund
inventory.returnItem(blackSweater);
// Then I should have four black sweaters in stock
assertEquals(4, inventory.countStock(BlackSweater.class));
}
Go ahead and implement the logic if you like. I’ve implement hard-coded returns in my code. Lets complete the second test (commit).
@Test
public void replacedItemsShouldBeReturnedToStock() {
Inventory inventory = new Inventory();
// Given that a customer buys a blue garment
BlueGarment blueGarment = new BlueGarment();
inventory.sellItem(blueGarment, new Customer());
// And I have two blue garments in stock
inventory.addItem(new BlueGarment());
inventory.addItem(new BlueGarment());
// And three black garments in stock.
inventory.addItem(new BlackGarment());
inventory.addItem(new BlackGarment());
inventory.addItem(new BlackGarment());
// When he returns the garment for a replacement in black,
inventory.replaceItem(blueGarment, inventory.get(BlackGarment.class));
// Then I should have three blue garments in stock
assertEquals(3, inventory.countStock(BlueGarment.class));
// And two black garments in stock
assertEquals(2, inventory.countStock(BlueGarment.class));
}
These test share both customer and inventory. We can refactor this out.
private Inventory inventory;
private Customer customer;
@Before
public void setUp() throws Exception {
inventory = new Inventory();
customer = new Customer();
}
Finally, we can implement the main code (commit). See that I’ve still got TODOs in the code, as I can clearly see some other scenarios that we’ll want to test.
I hope this shows you one interesting way to write tests and bring some new ideas to your code without having to depend on another technology to do so.