How do you test a packages-for-everything app?

So in my EmailSend package I expose a method like EmailSend.run() and then I create a second package like EmailSendRunner which calls EmailSend.run()? Is that what you mean?

Even if I do that, you canā€™t stub EmailSend.run() before loading the EmailSendRunner package via api.use('EmailSendRunner') inside the onTest() block, as far as I know. Thatā€™s the crux of my challenge I believeā€¦

Letā€™s say you have a FooMail package with a sendEmail method (i like it better than run, because the concerns are more separated).
Then you have a TaskScheduler package with a method to schedule task like that :

TaskScheduler.add(func, periodicity);

The important point is that TaskScheduler has NO dependency on FooMail. TaskScheduler has not to know about FooMail since its only duty is to schedule Tasks.

Now, in a third package, letā€™s call it Main or App, you require (api.use) FooMail and TaskScheduler and write something like :

TaskScheduler.add(FooMail.sendEmail, 10);

(edit : actually you have to use bind, but thatā€™s not the interesting point)

No if you want to unit test TaskScheduler, itā€™s easy to pass to it dummy ā€œtasksā€ or what you want.

Most of the time, easier testability leads to more decoupled design, which is a good side effect :slight_smile:

1 Like

@vjau Thanks for persevering. :smile:

Itā€™s getting a bit hard to discuss all this in English, so Iā€™ve resorted to code! Iā€™ve written up an example repo here. Itā€™s 2 packages. fake-email which just calls console.log(), and email-on-startup which does 2 things. First, it calls Meteor.startup() with a callback to call FakeEmail.send(). Secondly, it calls FakeEmail.send() immediately as soon as itā€™s loaded.

Running meteor test-packages email-on-startup shows that the first call to FakeEmail.send() can be successfully avoided in the test. To do that, itā€™s necessary to have the api.use('email-send'); in the onTest() block of package.js.

However, the second call, which is in email-on-startup.coffee line 10 cannot be avoided. This code is executed before the test code is run, and so at that point, FakeEmail.send() has not yet been stubbed.

This exercise has taught me that the test code is run before Meteor.startup() callbacks are run. Thatā€™s very useful to know. That would mean, so long as weā€™re careful, we could manually stub every dependency package in our test files, and then the real packages should never get called.

However, thatā€™s a very fragile, inelegant solution. If we miss any methods, then the real methods will be run. It also means we have to run everything via Meteor.startup() which may not be ideal in all situations.

Is there a better alternative that Iā€™m missing?

Some related reading :
http://misko.hevery.com/2008/07/08/how-to-think-about-the-new-operator/

I refactored you example separating :

  • the email sending facility (unit testable)
  • the startup executing facility (unit testable)
  • the app instantiating facility (not unit testable, like explained in the link).

One way Iā€™ve successfully stubbed packages with other packages, is by getting the stubbing package to depend on the original package. In so doing, Meteor loads the package to be stubbed first.

In your example, I would set your EmailFake to use Email. This is what I do for this xolvio:inbox-stub, which is an app-level email interceptor.

Hopefully that helps you

@vjau Great, thanks for the code. Now I know exactly what you mean. :smile: The app instantiating facility is not unit testable in your example because of limitations within Meteor, right? Thereā€™s nothing inherently ā€œuntestableā€ about that functionality as I understand it.

What Iā€™m wondering, is what to do about this. Do I simply not test this part of the code? Or do I create some alternate construct or pattern that allows me to test this? If I do, Iā€™m introducing more fragility into my code, because I will probably end up with test related code inside my production app.

@sam: Nice, I like this approach. Maybe the ultimate solution is to implement something ugly like if process.env.TESTING is true then stub the package, and always run my tests like TESTING=true meteor test-packages....

This could get pretty complex though. Iā€™d end up with something like my real package foo-core which is a dependency of an intermediary foo package. In that intermediary package, I conditionally stub or load the actual foo-core package. Again, Iā€™m including test related code in my production application, which increases fragility.

I think that this challenge fundamentally comes down to the fact that in Meteor, it is not possible to load a package under test without actually running the code in that package. There is no inbuilt solution for this. So I can either build something myself, or the code in my packages will always be run when my tests are run.

I suppose after this discussion my question comes down to whether or not I can detect the difference between meteor and meteor test-packages. I started a new thread to ask that question here:

Not quite :smile:

You can set the debugOnly flag to true, and this means any package you build this way will not be built in production. This is how we do it with Velocity and the MDG helped us by implementing that flag. Iā€™d give that a try first

Hey @chmac you could use something like Space.Application to start your code exactly when you want to (basically the same as having a main method).

The scenario you described is actually the very reason i started building the Space framework, I needed more control over the configuration and bootstrapping process. The Space framework gives you two phases you can control and hook into your app: 1. configuration and 2. startup. You can even do things like overriding complete subsystems with stubs during testing simply by hooking into the configuration step of your application.

If you want to test your sub-packages in various scenarios you can also create stub-apps for your integration tests that load and configure the modules in a certain way before running.

2 Likes

@sam Aha, thatā€™s a nice approach. It doesnā€™t solve the specific tests running vs meteor running in development issue, but itā€™s a nice way to make sure stubs never go to production.

@CodeAdventure Interesting approach, thanks for the link. Not sure if I want to go so far as to use Space, but it sounds like an interesting development. Iā€™d love to see something like this in meteor core. Some sort of main() function would be a real help for being able to write more testable code.

Hey @chmac, I guess you misunderstood what I was trying to say: Meteor has a main function! It is called Meteor.startup and it is your responsibility to structure your code in a way that there is only one place that calls it. All the tools are there, now be creative and build a great structure around them :wink: I guess if you never try a more solid way to structure your code, you will always end up with coupled spaghetti and typical hard-to-test Meteor code that is floating around a lot.

1 Like

@CodeAdventure OK, fair point, we could always hook into Meteor.startup(). I suppose if we always exposed the function that was called by Meteor.startup() then we could also stub and test it.

When we run meteor test-packages, the Meteor.startup() hooks are always called, so itā€™s not quite the same as the main() in Go, but we could leverage it to get pretty close.

The dependency tree gets pretty complex though. If package C depends on B which depends on A, then to test C in isolation, I need to stub all the calls to Meteor.startup() in both B and A. That could get really messy, and itā€™d be nearly impossible to test that we had actually stubbed all of those startup calls.

Anyway, youā€™re right, there are options available via Meteor.startup(), thanks for the clarification.

@chmac thereā€™s one catch: never use Meteor.startup in packages unless you make them completely configurable beforehand. I just had to ask @arunoda to add a hacky FlowRouter.wait API to allow me to start routing when my app is ready, not when the routing package thinks everything is ready. So I would even go so far to say that you actually should only use a single Meteor.startup call for the whole Meteor application if possible.

If you have package dependencies then your code should reflect that explicitly and not indirectly via hard-to-control Meteor.startup callbacks and hard-coupled globals. Either you make each package configurable, similar to iron-router or you will always end up with these problems.

Honestly, at this point in the discussion you should try the Space framework I showed you earlier :wink: it was specifically made to solve all of these dependency-tree / injection problems and to make your code unbelievably easy to structure and test. You can define modules and have other modules / the main app require them in a really, really simple way. It also does not matter if you use a package-only or ā€œnormalā€ Meteor app structure, it just works.

I also have to say that while it might looks daunting at first to use something like dependency injection, it really makes your code much more readable and the dependency graph explicit, which will save you many hours of pain in the long run.

If you want to read up the well established design patterns behind solve these problems:

  1. Inversion of Control
  2. Dependency Injection

These patterns are pretty much standard nowadays and not even Meteor can save us from writing our software with solid architectural principles in mind. Meteor provides a great build platform and many amazing packages to deal with realtime-sync and display of MongoDB data. Everything else is still up to you as application architect (and thatā€™s a lot!). I like to see Meteor as an implementation detail that should not leak into every line of code of my application.

2 Likes

@CodeAdventure Thanks for the follow up. Iā€™m reluctant to add a package such as Space as a dependency into my codebase. Then Iā€™m subject to any bugs / issues in that package, and my code simply wonā€™t work without it. It becomes as much of a dependency as Iron Router, maybe even moreso. Iā€™d want to see wide community support, broad usage, and so on, before doing that.

Ideally, Iā€™d like to see this sort of control within Meteorā€™s core. It looks (from initial glance) like the Space framework is a great solution, but a great solution thatā€™s implemented alongside or on top of meteor, rather than within it. Iā€™ll take a closer look at Space on the next Meteor project. :smile:

Yeah, of course packages like these require that you write your code with some other principles in mind than ā€œnormallyā€. Although I have to say, one really important aspect for me was that Space is not as invasive as e.g: React.

Basically you can write all of your app code in plain Javascript without any reference to the Space framework. You only have to add a Dependencies object your classes/prototypes (think: annotation) which tells the injector what you require. The cool thing is, you donā€™t need Space at all to test your business logic classes ā€“ you can simply provide the dependencies yourself during the tests, possibly some stubs or simplified implementations.

The Space modules / app are just a convenient sugar on top of the basic building blocks. You could also just use the Space.Injector with a completely custom way how your app is structured and initialized. And hey, if you come up with better solutions for some of the implementations, drop me a line ā€“ i am always happy to improve Space :wink:

Regarding bugs and issues ā€“ you wouldnā€™t be the first one to use Space for a production application and I am working on two big apps completely built with these tools every day. So this package (and the many others built on top of it) wonā€™t die any time soon and in fact i am constantly working to improve the details. Of course, everything is completely unit tested and written in a really simple style, so that bugs have a pretty hard time in the repo :stuck_out_tongue:

1 Like

@CodeAdventure that is the most meta package Iā€™ve ever seen. I had to re-read it at least three times to understand what it was doing. Youā€™ve made a framework inside of a framework. On top of Meteorā€™s namespacing, inheritance, and dependency system, on top of JavaScriptā€™s namespacing, inheritance, and dependency system, youā€™ve gone and added another layer of logic to understand and work within just to organize, reference, and reuse your code.

I donā€™t intend to be rude, but my honest opinion is that it seems like one huge anti-pattern to me. Instead of being more rigorous about your dependency injection, only having one Meteor.startup in your application, using proper stubs and function spies for testing, you made a system thatā€™s enabling your bad habits but saving your codebase from total mayhem.

To each his own, but geez is that a wacky package.

To @chmacā€™s original question ā€” in our applications, any code thatā€™s supposed to run on startup is exported through the package and invoked in the applicationā€™s Meteor.startup. We donā€™t put any startup calls in packages because itā€™s very obfuscated then as to whatā€™s actually happening when your application is running.

One of the cool things about Meteorā€™s packaging system is that the package manifests are dynamic (.js), rather than static (.json). So, like you mentioned earlier, you can conditionally exports symbols when testing. Hereā€™s a relevant snippet from one of our packages:

  // packages/grove-receiver/package.js
  if ( process.env.TESTING_FLAG === '1' ) {
    // In testing environment export these so they can be
    // stubbed/observed appropriately
    api.export([
      'ParseEvent',
      'Receiver',
      'Grove',
      'Groves'
    ], 'server');
  }

  api.export([
    'OpenGroveStream',
    'Spark',
  ], 'server');

This is from an application that has nothing in it except for the Meteor.startup on the server. This is literally the only file, the entire application:

/// server/main.js
Meteor.startup(() => {
  Spark.login({
    username: process.env.SPARK_USERNAME,
    password: process.env.SPARK_PASSWORD
  })
  .then( Meteor.bindEnvironment(function(token) {
    console.log('Logged into Spark:', token);
    OpenGroveStream();
  }))
  .catch(function(err) {
    console.error('Login to Spark failed:', err);
    Kadira.trackError('spark-login-error', err);
  });
});

We also maintain a package.json file for each of our applications so we can run tests much more easily. The startMongo and stopMongo stuff is because thereā€™s a separate database instance running in addition to the default one (needed when doing high-velocity database updates). With this, running tests are as simple as saying npm test. Or if you want to the nice HTML reporter with tests automatically re-running for a specific package, npm run pretty-receiver-tests.

// grove-cloud package.json
{
  "name": "grove-cloud",
  "version": "0.1.0",
  "description": "Meteor application for receiving published events from Grove hardware",
  "scripts": {
    "start" : "npm run startMongo && source config/development/env.sh && meteor",

    "stop": "npm run killMongo",

    "test" : "npm run receiver-tests",

    "receiver-tests" : "npm run startMongo && source packages/grove-receiver/tests/env.sh && meteor test-packages --velocity grove:grove-receiver && npm run killMongo",
    "pretty-receiver-tests" : "npm run startMongo && source packages/grove-receiver/tests/env.sh && meteor test-packages --driver-package respondly:test-reporter grove:grove-receiver && npm run killMongo",

    "startMongo": "mongod --smallfiles --dbpath .db --fork --logpath /dev/null",
    "killMongo": "mongo admin --eval 'db.shutdownServer()'"
  },
}

1 Like

Hey @louis! Yep you got it: space:base is a meta package and tries to fill a gap that many struggle with when starting with Meteor: how to structure your app and manage dependencies. To be honest, you get the biggest benefit from the module/app system of Space when you donā€™t split up your app into sub-packages like in this basic TodoMVC example. Because most people run into load-order issues pretty soon.

Youā€™re right, if you start with a package-only structure from the beginning you probably wonā€™t need the DI stuff if thatā€™s not your cup of tea. However, I donā€™t see a lot of ā€œbad habitsā€ there, it merely makes patterns explicit instead of the implicit nature of many other packages. Think about it: a lot of packages out there basically boil down to a static singleton pattern. Even Meteor uses it for most of its APIs and in my opinion i like the explicit approach more: Define your classes like normal and make them singletons during runtime. This way you can interact with them in your tests without having to stub your whole environment.

Thanks for the critical input ā€“ I will think about making the DI system more optional, so that you can use all the features of the various space packages without using the app/module system.

Sidenote:
May I ask you why you use Promises on the server? For me the biggest feature of Meteor is that it uses fibers under the hood and I can treat async code like synchronous code.

1 Like

@louis Awesome. Thanks a lot. Thatā€™s exactly what I was looking for. Practical insights into how other folks are solving this problem. I like your approach a lot. I particularly like the package.json idea, I hadnā€™t thought about including that.

Presumably your env.sh file sets TESTING_FLAG='1'. I was a bit concerned about that flag being missed when we run tests locally, but adding it into package.json is an elegant solution.

There is also a testOnly flag on api.export() which can export values only when that package itself is under test. It looks like api.export('Foo', ['server'], {testOnly: true});. I donā€™t think itā€™s very widely documented, but Iā€™ve personally tested it, and itā€™s working for us.

Iā€™m leaning towards the approach of exporting startup functions from all my packages, wrapping them in a startup function inside my app package (which I can test) and then having a single line in my application itself that just calls app's startup function. Then almost everything is testable, and it requires no external dependencies.

PS> Grove looks like an awesome project, you guys need to launch in Berlin!

3 Likes

No problem, youā€™re welcome. I had NO idea that testOnly flag existed, thanks! Oh documentationā€¦ :sweat_smile:

And thanks for the note about Grove! :blush: I canā€™t wait till we can get them all around the world; hopefully the app will be much better by the time we reach Germany :laughing:

Hey @CodeAdventure! Ok, I understand your package a bit better now. So in the applications that you use Space on, do you eschew Meteor packages and make your own with space:base?

Canā€™t wait for official module support, gonna be way moā€™ bettaā€™. Less magic about where stuff is coming from, way easier to look at a single file and reason about whatā€™s happening.

I use Promises on the server because itā€™s a clean syntax and now a core part of Javascript. Fibers are a really cool invention, but too hard to explain and understand for a lot of developers. MDG is actually replacing all of the Futures in Core with Promises automatically wrapped by a Fiber. Youā€™d probably like this thread -> Fibers and meteor-promise npm pacakge

1 Like