mauricemach / zappa

Node development for the lazy.
zappajs.org
MIT License
949 stars 81 forks source link

How to structure code for unit testing in zappa? #88

Closed dimroc closed 13 years ago

dimroc commented 13 years ago

Hi, I was curious as to how one would recommend architecting / laying out code for server side and client side unit testing.

As it stands now, for my server side unit testing, I've been exporting (exports.) my different layers and having the unit test require them. And then having a separate @include that also require's the export, with this include getting pulled in by the app.coffee file.

example:

models.coffee

collab_docs_factory = (is_logging_to_console) ->
  logger = require('log4js').getLogger()  
  # collab_docs_factory defined here ....
...

@include = ->
  # The difficulty in containing the 'this' pointer and knowing when or what dynamic code
  # has been loaded has led to this weird model below where I'm literally importing myself
  # to call the factory function. 
  # Investigate taking a class based solution to this so I can encapsulate code in one class
  # more elegantly.
  model_factory = require 'models'
  @collab_docs = model_factory.create_collab_docs()
  def collab_docs: @collab_docs

# allow hook for unit tests to access model layer via 'require'
exports.create_collab_docs = collab_docs_factory

models.test.coffee

vows = require 'vows'
assert = require 'assert'
model_factory = require '../models'

# unit/tdd tests go here ...

If there is a more elegant approach I'd love to hear it. As it stands now, it seems I need a factory function/@include pair for anything I'm interested in testing and including.

Thanks! How does zappa unit test?

mauricemach commented 13 years ago

Hey Dimitri,

Why are you using include in this case anyway? It's useful when you want to run a lot of zappa stuff such as get and at, but for everything else, I think good ol' require is the best tool for the job.

app.coffee:

require('zappa') {require}, ->
  def collab_docs: require('models').create_collab_docs()
dimroc commented 13 years ago

Interesting.

I'm currently using include because I'm pulling all these files inside app.coffee as shown below. Assuming the def I use with the require will exist throughout zappa, I'll take another hard look at my code layout.

Open to all suggestions :)


port = Number(process.env.VMC_APP_PORT || process.env.C9_PORT || process.env.PORT || 3000)
zappa = require('zappa')

zappa port, ->
  log4js = require('log4js')
  logger = log4js.getLogger()

  enable 'serve jquery'
  io.set 'transports', ['xhr-polling', 'websocket', 'flashsocket', 'htmlfile']
  io.set 'log level', 2

  publicDir = __dirname + '/public'
  use log4js.connectLogger logger
  use 'bodyParser', 'cookieParser', express.session({secret: 'collaborative coffee'})
  use 'methodOverride', app.router
  use express.compiler(src: publicDir, enable: ['sass', 'coffeescript'])
  use 'static'

  configure
    development: -> use errorHandler: {dumpExceptions: on, showStack: on}
    production: -> use 'errorHandler'

  # a collection of views and functionality used in subsequent includes.
  include 'base.coffee'

  # handles all the schemas and models used app side mapped to a mongo collection/document.
  include 'models.coffee'

  # actual resources for the website.
  include 'collabs.server.coffee'
  include 'collabs.client.coffee'
  include 'collabs.view.coffee'

  include 'root.coffee'
mauricemach commented 13 years ago

def is gone with 0.3.0, so I guess one more reason to use require instead of @include unless you specifically need it.

dimroc commented 13 years ago

Sorry Maurice, not sure I understand.

Whether I use require or @include, I still use a def. How does the removal of def act as a reason to use require?

and with def gone, what construct would I want to use to have a variable span across the server side of my zappa app?

mauricemach commented 13 years ago

With 0.3.0, def is gone because you don't need it anymore. We have normal JS scoping rules back.

http://zappajs.org/docs/0.3-gumbo/announcement/

Just declare a variable without def and your handlers in the same file will be able to "see" it.

Now, if you want a variable to be available to all your handlers across all files, you have a couple of options. If it's a helper function, you could use @helper foo: ->. It will be available to your request and socket handlers as @foo. If it's an app setting, consider using @set foo: 'bar' (available as @settings.foo).

Note: I meant the removal of def is a reason not to use @include.

dimroc commented 13 years ago

Thanks for the info! Looking forward to the next release.