This starter kit was made to help students to develop red_tetris project : a Full Stack Javascript Tetris. We can also use it as a starting point for any product made of React / Redux and socket.io.
It helps:
- to transpile with Babel ES6 code
- to bundle with Wbepack JS files and hot reload client's code
- to write tests and check code coverage.
Because we use React, Redux, Node.js and Socket.io, we had to define 3 kinds of unit tests :
- React ones like explained in redux documentation +
chai-equal-jsx
- Redux ones, but instead of just testing pure functions, we defined a middleware to test state’s impact after one or many actions.
- Redux/Socket.io/Node.js, same as before, we use the same middleware but this time we can test state’s updates after socketio messages round trip.
Install node first. After that:
$ npm install
Edit params.js
for your needs.
$ npm run srv-dev
> [email protected] srv-dev /home/eric/JS/red_tetris_boilerplate
> DEBUG=tetris:* babel-watch -w src src/server/main.js
It launches a node.js server listening for socket.io connexions, that is wired to receive ping
messages and answered to … pong
.
$ npm run client-dev
> [email protected] client-dev /home/eric/JS/red_tetris_boilerplate
> webpack-dev-server --colors --hot --inline --host 0.0.0.0 --port 8080
http://0.0.0.0:8080/
webpack result is served from /
content is served from /home/eric/JS/red_tetris_boilerplate
…
webpack: bundle is now VALID.
Point your browser to http://0.0.0.0:8080/
it will load client side application. You should see Soon, will be here a fantastic Tetris ...
, open your console and check you have :
[HMR] Waiting for update signal from WDS...
bundle.js:28328 action @ 14:29:58.602 ALERT_POP
bundle.js:28340 prev state Object
bundle.js:28344 action Object
bundle.js:28352 next state Object
bundle.js:616 [WDS] Hot Module Replacement enabled.
URL is not yet editable in params.js
, change it directly inside package.json
.
As you can guess we are using webpack hot reload
module, try to update any file under src/client
and your browser should reload your code.
[WDS] App updated. Recompiling...
Test, test and re-test …
Stop server, or use an other setup (//TODO)
$ npm run test
Tests are installed under test
folder.
A simple template to implement simple unit tests. In Tetris context you will try to test every functions or classes from server or client code. Just import your files and check (http://shouldjs.github.io/)[should] documentation to extend the test.
Target is to test actions
and reducers
in one time. You can always split those tests as explained here.
Look at the code :
//cat redux1.js
// 1
import {configureStore} from './helpers/server'
// 2
import rootReducer from '../src/client/reducers'
import {ALERT_POP, alert} from '../src/client/actions/alert'
import chai from "chai"
const MESSAGE = "message"
chai.should()
describe('Fake redux test', function(){
it('alert it', function(done){
const initialState = {}
// 3
const store = configureStore(rootReducer, null, initialState, {
ALERT_POP: ({dispatch, getState}) => {
const state = getState()
state.message.should.equal(MESSAGE)
done()
}
})
// 4
store.dispatch(alert(MESSAGE))
});
});
- We use a special middleware to set up hooks in action’s workflow.
- We use here the root reducer, but it can be replaced by any kind of reducer
- target is to check updates in our store, so we have to create a store for each check (
it()
),configureStore
is a store helper.
configureStore :
reducer
: not necessary the root onesocket
: (unused here)initial state
: set up to realize the actionactions hook
: object where keys are action’s type and values are callbacks.action’s type
is one of your actions defined in your application,callback
function will receive {getState, dispatch, action} as real parameter.
Thanks to the hook you can react to actions, just to check a new state after an action, or to send actions to follow a workflow and check state at the end.
In our sample, we register a callback when ALERT_POP
will be dispatched and check that state.message
is right. Callback is called after reducers.
Very similar to previous test, but offer to test server code involved in a client action. You can use this kind of solution to test a pipeline like action -> fetch -> action -> reducer
. Here client / server communication is based on socket.io and we use a middleware inspired by redux-socket.io to transparantly dispatch and receive socket.io messages. So our test covers action -> socket.emit -> server code -> client socket callback -> action -> reducer
. I do not know if it’s still a unit test, but it’s a useful solution to test.
Let’s have a look on code:
import chai from "chai"
import {startServer, configureStore} from './helpers/server'
import rootReducer from '../src/client/reducers'
// 1
import {ping} from '../src/client/actions/server'
import io from 'socket.io-client'
import params from '../params'
chai.should()
describe('Fake server test', function(){
let tetrisServer
// 2
before(cb => startServer( params.server, function(err, server){
tetrisServer = server
cb()
}))
after(function(done){tetrisServer.stop(done)})
it('should pong', function(done){
const initialState = {}
const socket = io(params.server.url)
// 3
const store = configureStore(rootReducer, socket, initialState, {
'pong': () => done()
})
store.dispatch(ping())
});
});
- This time we will test server actions: it means client actions that transparently communicate with server
- for each
describe
we have to launch the server. Tetris server is statefull, so we can run multiple tests (it
) on one server to check behavior (ex: multiple users, events) - Now we have a socket (client connection), so middleware is able to send socket.io messages to server.
In our context, we dispatch ping
action and register a callback on pong
action.
npm run coverage
> [email protected] coverage /home/eric/JS/red_tetris_boilerplate
> NODE_ENV=test nyc -r lcov -r text mocha --require babel-core/register
Check results …. of this command, and launch your browser to ./coverage/lcov-report/index.html
It’s not a production recipe to run your Tetris over billions of players, but just 2 commands to run it without live reload.
$ npm run srv-dist
> [email protected] srv-dist /home/eric/JS/red_tetris_boilerplate
> DEBUG=tetris:* babel src --out-dir dist
src/client/actions/alert.js -> dist/client/actions/alert.js
src/client/actions/server.js -> dist/client/actions/server.js
src/client/components/test.js -> dist/client/components/test.js
src/client/containers/app.js -> dist/client/containers/app.js
src/client/index.js -> dist/client/index.js
src/client/middleware/storeStateMiddleWare.js -> dist/client/middleware/storeStateMiddleWare.js
src/client/reducers/alert.js -> dist/client/reducers/alert.js
src/client/reducers/index.js -> dist/client/reducers/index.js
src/server/index.js -> dist/server/index.js
src/server/main.js -> dist/server/main.js
$ npm run client-dist
> [email protected] client-dist /home/eric/JS/red_tetris_boilerplate
> NODE_ENV=production webpack --progress --colors
Hash: 6841f78bfe6867fb2913
Version: webpack 1.13.0
Time: 1923ms
Asset Size Chunks Chunk Names
bundle.js 754 kB 0 [emitted] main
+ 197 hidden modules
$ DEBUG=tetris:* node dist/server/main.js
tetris:info tetris listen on http://0.0.0.0:3004 +0ms
not yet ready to play tetris with U ...
In production mode, node.js server serves index.html
and bundle.js
, so you have to point to url set up in params.js
That’s all folks ...