Yes I know, test first, red/green/refactor, but...
Should I test the behavior of each class?
or
Should I test the whole system together?
None of them! Just BDD!
Feature: Display order price
Background:
Given that the order total price is "1000" "USD"
Scenario: USA user
Given that the user is from "USA"
When the user displays the order
Then "$1,000.00" will be shown as total price
Feature: Validate add product form
Scenario: success
When the user fills " 1000 " in the quantity field
Then the quantity field will not be highlighted as erroneous
And the quantity field will show "1,000"
Scenario: invalid quantity
When the user fills "one hundred" in the quantity field
Then the quantity field will be highlighted as erroneous
And the quantity field will show "one hundred"
And an "invalid quantity (one hundred)" message will appear
Feature: User adds a product
Scenario:
When the user adds "2 cappucino" to the order
Then an add product "2 cappucino" request will be sent to the server
And the add product form will be disabled
And a progress bar will be shown
Feature: Render order
Scenario:
When the following order is rendered:
| contents | total price | enabled forms |
| 2 Cappucinos, 1 Expresso | Ten dollars | addProduct, completeOrder |
Then the total price field will contain "Ten dollars"
And the following items will be shown:
| product | quantity |
| Cappucinos | 2 |
| Expresso | 1 |
And the "addProduct, completeOrder" forms will be enabled
And the "removeProduct, cancelOrder" forms will be disabled
before(function () {
this.driver.get('http://localhost:8888/testpage.html');
return this.driver.executeAsyncScript(function () {
var done = arguments[0];
window.view = require('order-view')('.container', done);
});
});
afterEach(function() {
return this.driver.executeScript(function () {
document.querySelector('.container').innerHTML = '';
});
});
describe('when an order is rendered', function () {
var orderViewModel = { price: 'Ten dollars' /*, more stuff */ };
beforeEach(function () {
return this.driver.executeAsyncScript(function () {
var viewModel = arguments[0],
done = arguments[1];
view.render(viewModel, done);
}, orderViewModel);
});
// we will see in a moment
});
describe('when an order is rendered', function () {
// the set up we just saw
it('the total price field will contain "Ten dollars"', function () {
var priceElement = this.driver.findElement({
css: '.container .order .price'
});
return expect(priceElement.getText())
.to.eventually.be.equal(viewModel.price);
});
// the rest of the tests
});
Feature: Request to add a product
Background:
Given that the "product" input has been filled with "Cappucino"
And that the "quantity" input has been filled with "error"
Scenario: using enter
Given that the "product" input has focus
When the user press "ENTER"
Then the user request to add "error Cappucino"
Scenario: using button
When the user clicks the "submit" button
Then the user request to add "error Cappucino"
before(function () {
return this.driver.executeAsyncScript(function () {
var newOrderView = require('order-view'),
someTestOrderViewModel = arguments[0],
cb = arguments[1];
window.uxLogic = {
addBeverage: sinon.spy()
};
newOrderView('.container', window.uxLogic)
.render(someTestOrderViewModel, cb);
}, someTestOrderViewModel);
});
beforeEach(function () {
this.driver.findElement({
css: '.container .order .add-product input[name="beverage"]'
}).sendKeys('Cappuccino');
return this.driver.findElement({
css: '.container .order .add-product input[name="quantity"]'
}).sendKeys('2');
});
afterEach(function () {
driver.findElement({
css: '.container .order .add-product input[name="beverage"]'
}).clear();
driver.findElement({
css: '.container .order .add-product input[name="quantity"]'
}).clear();
return driver.executeScript(function () {
uxLogic.addBeverage.reset();
});
});
this.driver.findElement({
css: '.container .order .add-product input[name="quantity"]'
}).sendKeys(Key.ENTER);
this.driver.findElement({
css: '.container .order .add-product button[name="addToOrder"]'
}).click();
return this.driver.executeScript(function () {
var expectedParameters = arguments[0];
expect(uxLogic.addBeverage)
.to.have.been.calledWith(expectedParameters);
}, expectedParameters);
function newOrderPageObject(driver, containerSel) {
return {
totalPrice: function () {
return driver.findElement({
css: containerSel + ' .order .price'
}).getText();
}
};
};
function newProductLinePageObject(webElement) {
return {
info: function () {
return Promise.all([
webElement.findElement({ css: '.name' }).getText(),
webElement.findElement({ css: '.quantity' }).getText()
]).then(function (fields) {
return {
name: fields[0],
quantity: fields[1]
};
});
}
};
};
function newFormPageObject(webElement) {
return {
typeText: function (fieldName, text) {
return webElement.find({css: '[name="' + fieldName + ']"'})
.sendKeys(text);
},
pressKey: function (fieldName, keyName) {
return webElement.find({css: '[name="' + fieldName + '"]'})
.sendKeys(webdriver.Keys[keyName]);
},
clickSubmit: function () {
return webElement.find({css: '[type="submit"]'}).click();
}
};
};
function newOrderPageObject(driver, containerSel) {
return {
totalPrice: function () { /* ...skipped for brevity */ },
addProductForm: function () {
return newFormPageObject(driver.findElement({
css: containerSel + ' .order .add-product'
}));
},
productLine: function (i) {
return newProductLinePageObject(driver.findElement({
css: containerSel + '.product-line:nth-of-type(' + (i + 1) + ')'
}));
}
};
};