Obey the Testing Goat NodeJS edition!

Bootstrapping a project

In which we go from having nothing to having feature tests and knowing what our project will look like. Make sure you're on board with where we're going after reading the overview, then pull up a shell and start reading code!

0000000 From an empty folder

Before I even got to the first commit, I chose a couple basic approaches to getting a task runner working. My shell session went something like:

			$ grunt
			grunt-cli: The grunt command line interface. (v0.1.9)
			
			Fatal error: Unable to find local grunt.
			
			If you're seeing this message, either a Gruntfile wasn't found or grunt
			hasn't been installed locally to your project. For more nformation about
			installing and configuring grunt, please see the Getting Started guide:
			
			http://gruntjs.com/getting-started
			$ npm i --save-dev grunt
			# Install output
			$ grunt
			A valid Gruntfile could not be found. Please see the getting started guide for
			more nformation on how to configure grunt: http://gruntjs.com/getting-started
			Fatal error: Unable to find Gruntfile.
			$ touch Gruntfile.coffee
			$ grunt
			Warning: Task "default" not found. Use --force to continue.
			
			Aborted due to warnings.
			$ vi Gruntfile.coffee
			$ grunt
			Warning: Task "test" not found. Use --force to continue.
			
			Aborted due to warnings.
			$ vi Gruntfile.coffee
			$ grunt
			Warning: Task "cucumberjs:Edith" not found. Use --force to continue.
			
			Aborted due to warnings.
		

and so on, until I had about the Gruntfile you see here.

9bc7f50 Edith's first feature!

			.gitignore                              |  1 +
			Gruntfile.coffee                        | 14 ++++++++++++++
			package.json                            | 17 +++++++++++++++++
			test/behavior/steps/edith.coffee        | 16 ++++++++++++++++
			test/behavior/support/world.coffee      | 14 ++++++++++++++
			test/behavior/users/edith/Edith.feature |  9 +++++++++
			6 files changed, 71 insertions(+)
		

The first commit starts with a minimal feature to start our todo app, and just enough grunt to run some feature specs.

			diff --git a/Gruntfile.coffee b/Gruntfile.coffee
			new file mode 100644
			@@ -0,0 +1,14 @@
			+	module.exports = (grunt)->
			+		grunt.initConfig
			+			cucumberjs:
			+				Edith:
			+					files: src: ['test/behavior/users/edith']
			+					options:
			+						steps: 'test/behavior/steps'
			+
			+		grunt.npmTasks = [ "grunt-cucumber" ]
			+
			+		grunt.loadNpmTasks npmTask for npmTask in grunt.npmTasks
			+
			+		grunt.registerTask "test", [ "cucumberjs:Edith" ]
			+		grunt.registerTask "default", ["test"]
		
			diff --git a/test/behavior/users/edith/Edith.feature b/test/behavior/users/edith/Edith.feature
			new file mode 100644
			@@ -0,0 +1,9 @@
			+	Feature: Edith
			+		As a user of our site
			+		Edith wants to see the site
			+		So that she knows it exists
			+
			+		Scenario: Direct Browsing
			+			Given Edith has her browser open
			+			When Edith goes to the url directly
			+			Then she should see "Angular JS" in the title
			\ No newline at end of file
		

This is an incredibly primitive first feature, more a hello world than an app, but it does give us experience with our first two technologies: Grunt JS for running tasks and CucumberJS for running cucumber specs in the NodeJS environment.

When we run the grunt file at this point, we get an expected error:

			Running "cucumberjs:Edith" (cucumberjs) task
			.connect ECONNREFUSED Error: connect ECONNREFUSED
			    at errnoException (net.js:901:11)
			    at Object.afterConnect [as oncomplete] (net.js:892:19)
			F-

			(::) failed steps (::)

			Error: connect ECONNREFUSED
			    at errnoException (net.js:901:11)
			    at Object.afterConnect [as oncomplete] (net.js:892:19)

			Failing scenarios:
			/home/southerd/devel/southerd/tdd/angular/tdd-angular/test/behavior/users/edith/Edith.feature:6 # Scenario: Direct Browsing

			1 scenario (1 failed)
			3 steps (1 failed, 1 skipped, 1 passed)
		

Moving on, the most basic server that still returns HTML.

3da6857 Minimal server.

			Gruntfile.coffee          | 16 ++++++++++++++--
			package.json              |  7 ++++++-	
			server/server.coffee      |  9 +++++++++
			server/test/serves.coffee | 34 ++++++++++++++++++++++++++++++++++
			4 files changed, 63 insertions(+), 3 deletions(-)
		
			diff --git a/Gruntfile.coffee b/Gruntfile.coffee
			@@ -1,14 +1,26 @@
			module.exports = (grunt)->
				grunt.initConfig
			+		mochaTest:
			+			server:
			+				options:
			+					reporter: 'spec'
			+				src: ["server/test/*coffee"]
			+
					cucumberjs:
						Edith:
							files: src: ['test/behavior/users/edith']
							options:
								steps: 'test/behavior/steps'
			
			-			grunt.npmTasks = [ "grunt-cucumber" ]
			+		grunt.npmTasks = [
			+			"grunt-cucumber"
			+			"grunt-mocha-test"
			+		]
			
					grunt.loadNpmTasks npmTask for npmTask in grunt.npmTasks
			
			-			grunt.registerTask "test", [ "cucumberjs:Edith" ]
			+		grunt.registerTask "test", [
			+			"mochaTest:server"
			+			"cucumberjs:Edith"
			+		]
				grunt.registerTask "default", ["test"]
		
			diff --git a/server/test/serves.coffee b/server/test/serves.coffee
			new file mode 100644
			@@ -0,0 +1,34 @@
			+	should = require "should"
			+	server = require "../server"
			+	request = require "request"
			+	describe "Server", ->
			+		index = (d, cb)->
			+		request "http://localhost:3000/", (e, r, b)->
			+			cb e, r, b
			+			d()
			+	
			+		before ->
			+			server.serve()
			+	
			+		it "binds on a known port", (done)->
			+			index done, (err, response)->
			+				should.not.exist err, "Error when GETting (#{err})"
			+	
			+		it "returns 200 when requesting /", (done)->
			+			index done, (e, res)->
			+				res.statusCode.should.equal 200
			+	
			+		it "returns an index page at /", (done)->
			+			index done, (e, r, body)->
			+				body.should.match ///
			+					^<html>.*
			+					</html>$
			+				///,
			+				"page needs basic HTML structure."
			+	
			+		it "returns a page with a title", (done)->
			+			index done, (e, r, body)->
			+				body.should.match ///
			+					<title>[^<]*Angular\sJS[^<]*</title>
			+				///,
			+				"page needs a title"
		
			diff --git a/server/server.coffee b/server/server.coffee
			new file mode 100644
			@@ -0,0 +1,9 @@
			+	express = require "express"
			+	app = express()
			+	
			+	app.get '/', (req, res)->
			+		res.send "200", "<html><title>Angular JS</title></html>"
			+	
			+	module.exports =
			+		serve: ->
			+			app.listen(3000)
			\ No newline at end of file
		

A super super simple HTTP server that just returns a hardcoded bit of HTML that meets the minimal requirements. Of course, the code was written as a back and forth between each test, in order, and the next feature of the server. So exporting app.listen(3000) got written first, and later the app.get '/', (req, res)->. I decided at this point to use Express instead of Node.HTTP in anticipation of needing more capabilities in the future. From a TDD standpoint, it doesn't matter - in fact, it took less code to get the express server running than using Node.HTTP, so that's a TDD win!

79f7a81 Added "Build More" scenario.

			test/behavior/steps/browsing.coffee     | 2 +	-	
			test/behavior/steps/buildmore.coffee    | 6 ++++++
			test/behavior/users/edith/Edith.feature | 5 +++++
			3 files changed, 12 insertions(+), 1 deletion(-)
		
			diff --git a/test/behavior/users/edith/Edith.feature b/test/behavior/users/edith/Edith.feature
			--- a/test/behavior/users/edith/Edith.feature
			@@ -7,3 +7,8 @@ Feature: Edith
						Given Edith has her browser open
						When she goes to the site directly
						Then she should see "Angular JS" in the title
			+	
			+		Scenario: More Features!
			+			Given Edith has her browser open
			+			When she goes to the site
			+			Then she wants MORE FEATURES!
			\ No newline at end of file
		

I like the always-failing "Build More!" feature as a reminder to keep on working when running the tests after coming back to the project.

5ae148a Refactored HTML to index.html file.

			client/index.html         | 7 +++++++
			server/server.coffee      | 3 ++-	
			server/test/serves.coffee | 7 ++-----
			3 files changed, 11 insertions(+), 6 deletions(-)
			
		

The first big refactor was pulling the hardcoded HTML into its own file. Notice it also required a change in the test.

			diff --git a/client/index.html b/client/index.html
			new file mode 100644
			@@ -0,0 +1,7 @@
			+	<html>
			+	<head>
			+		<title>Angular JS</title>
			+	</head>
			+	<body>
			+	</body>
			+	</html>
			\ No newline at end of file
		
			diff --git a/server/server.coffee b/server/server.coffee
			--- a/server/server.coffee
			@@ -1,8 +1,9 @@
				express = require "express"
				app = express()
			+	path = require "path"
			
				app.get '/', (req, res)->
			-			res.send "200", "<html><title>Angular JS</title></html>"
			+			res.sendfile path.join __dirname, "..", "client", "index.html"
			
				module.exports =
					serve: ->
		
			diff --git a/server/test/serves.coffee b/server/test/serves.coffee
			--- a/server/test/serves.coffee
			@@ -20,11 +20,8 @@ describe "Server", ->
			
				it "returns an index page at /", (done)->
					index done, (e, r, body)->
			-			body.should.match ///
			-				^<html>.*
			-				</html>$
			-			///,
			-			"page needs basic HTML structure."
			+			body.should.match /^<html>/
			+			body.should.match /<\/html>$/
			
				it "returns a page with a title", (done)->
					index done, (e, r, body)->
			
		

09957a3 Added watch task.

			Gruntfile.coffee | 12 ++++++++++++
			package.json     |  3 ++-	
			2 files changed, 14 insertions(+), 1 deletion(-)
		

Watch tasks are a great way to ensure the latest code is always built and running while iterating rapidly. Of course, the build has to run very quickly.

			diff --git a/Gruntfile.coffee b/Gruntfile.coffee
			@@ -12,9 +12,21 @@ module.exports = (grunt)->
						options:
							steps: 'test/behavior/steps'
			
			+			watch:
			+				all:
			+					files: [
			+						'test/**/*coffee'
			+						'server/**/*coffee'
			+						'client/**/*html'
			+						'client/**/*coffee'
			+						'client/**/*less'
			+					]
			+					tasks: ['default']
			+	
				grunt.npmTasks = [
					"grunt-cucumber"
					"grunt-mocha-test"
			+			"grunt-contrib-watch"
				]
			
				grunt.loadNpmTasks npmTask for npmTask in grunt.npmTasks
		

c2c7dba Check for input box availability.

			client/index.html                       |  8 +++++-
			server/test/serves.coffee               | 15 ++++++++++++
			test/behavior/steps/browsing.coffee     | 35 ++++++++++++++++++++++-----
			test/behavior/support/world.coffee      | 11 +++++----
			test/behavior/users/edith/Edith.feature | 43 ++++++++++++++++++++++++++++++---
			5 files changed, 97 insertions(+), 15 deletions(-)
		
			diff --git a/client/index.html b/client/index.html
			@@ -1,7 +1,13 @@
				<html>
				<head>
			-		<title>Angular JS</title>
			+		<title>To Do - Angular JS</title>
				</head>
				<body>
			+		<header>
			+			<h1>To Do</h1>
			+		</header>
			+		<section>
			+			<input type="text" placeholder="to-do" />
			+		</section>
				</body>
				</html>
			\ No newline at end of file
		
			diff --git a/test/behavior/users/edith/Edith.feature b/test/behavior/users/edith/Edith.feature
			@@ -1,4 +1,4 @@
			-	Feature: Edith
			+	Feature: Site is available
				As a user of our site
				Edith wants to see the site
				So that she knows it exists
			@@ -6,9 +6,46 @@ Feature: Edith
				Scenario: Direct Browsing
					Given Edith has her browser open
					When she goes to the site directly
			-			Then she should see "Angular JS" in the title
			+			Then she should see "Angular JS" in the "title"
			+	
			+	Feature: Site loads
			+		As a fly-fishing enthusiest
			+		Edith wants the site to load
			+		So she can list her fly-fishing to-do list
			+	
			+		Scenario: Landing bootstrap
			+			Given Edith has her browser open
			+			When she goes to the landing page
			+			Then she should see "To Do" in the "title"
			+			And she should see "To Do" in the "header"
			+			And she should be invited to enter a "to-do" item
			+	
			+		Scenario: First to-do
			+			Given Edith is on the landing page
			+			When she enters "Buy peacock feathers" into a "text" box
			+			Then the page shows "1. Buy peackock feathres"
			+	
			+		# Scenario: Second to-do
			+		# 	Given Edith has entered a "to-do" item
			+		# 	And she is invited to enter a "to-do" item
			+		# 	When she enters "Use peacock feathers to make a fly" into a "text" box
			+		# 	Then the page shows "1. Buy peackock feathres"
			+		# 	And the page shows "2. Use peacock feathers to make a fly"
			+		# 	And she should be invited to enter another "to-do" item
			+	
			+		# # Edith wonders whether the site will remember her list. Then she sees
			+		# # that the site has generated a unique URL for her -- there is some
			+		# # explanatory text to that effect.
			+		# Scenario: Saves list
			+		# 	Given Edith has entered a "to-do" item
			+		# 	And she leaves the page
			+		# 	When she goes to the "saved" url
			+		# 	Then the page shows "1. Buy peackock feathres"
			+		# 	And the page shows "2. Use peacock feathers to make a fly"
			+		# 	And she should be invited to enter another "to-do" item
			+		# 	# Satisfied, she goes back to sleep
			
				Scenario: More Features!
					Given Edith has her browser open
					When she goes to the site
						Then she wants MORE FEATURES!
		

As you can see, I'm not as good a TDD as I'd like to be. This commit conflagrates finishing out the ToDo title post with the next set of scenarios. It also has a bunch of commented out scenarios - it would be better to use Cucumber attributes, instead of commenting them out. I didn't learn how to do that for another few commits.

9979623 Added placeholder to input.

client/index.html | 2 +	-	
			1 file changed, 1 insertion(+), 1 deletion(-)
		
			diff --git a/client/index.html b/client/index.html
			@@ -7,7 +7,7 @@
				<h1>To Do</h1>
				</header>
				<section>
			-		<input type="text" placeholder="to-do" />
			+		<input type="text" name="to-do" placeholder="to-do" />
				</section>
				</body>
				</html>
			\ No newline at end of file
		

A quick change gets all the basic features passing, and we're ready to start writing actual dynamic application code.

Next up, an app!