A custom JUnit Java Runner annotation to run headless Javascript tests embedded in your IDE
This repository has been archived on 2022-07-06. You can view files and clone it, but cannot push or open issues or pull requests.
Go to file
Wouter Groeneveld 7ea8dc7ce4 moved to other package name 2011-06-26 22:10:10 +02:00
src moved to other package name 2011-06-26 22:10:10 +02:00
.gitignore let @Before/@After work for superclasses 2011-06-26 21:44:55 +02:00
README.md added more readme stuff 2011-06-25 13:23:41 +02:00
pom.xml possibility to use @Before/After in superclasses 2011-06-26 22:05:07 +02:00

README.md

Jasmine Junit Runner

What's this?

Quite simple, it's a custsom Java Junit Runner that allows you to embed Javascript Unit tests (using Jasmine) in your Java-based projects. It fully integrates with your most beloved IDE, your most hated version control system and of course your most needed CI env.

So let's rephrase:

  • Run Javascript (the Jasmine - behavior driven - way) "specs" in Java
  • Talks like a duck-erhm, any other Junit Java test. Just use a custom annotation (see below)
  • Executes super-fast. No browser required. Hocus-pocus. (Rhino + Envjs magic)

Does this thing generate Junit XML?

Yes and no. Not explicitly using the Jasmine Junit XML Reporter, but since it's a Java Junit Result, your build process will do that for you. Maven surefire plugins will generate the needed result files, for Jenkins to pick up. Your stacktrace/failure message will be something like:

Expected x to be y (zz.js, #458)

Just like the default Jasmine HTML reporter.

What Do I need to do?

  1. Fork this project.
  2. Create some Jasmine specs, place them in some folder.
  3. Create a Junit test class, annotate it with @RunWith(JasmineTestRunner.class)
  4. Fill in the blanks using @JasmineSuite

More options

@JasmineSuite allows you to set these options:

  • debug: use the built-in Rhino debugger (gives you the chance to set a breakpoint before firing the test suite)
  • jsRootDir: the javascript install root dir. Jasmine and other should be installed here (see source)
  • sourcesRootDir: your production JS files root dir.
  • specs: one or more spec file to run. Default behavior: use java Class name (replaces Test with Spec, see example)
  • sources: one or more JS production file which your spec needs (included before specs, d'uh)
  • generateSpecRunner: (the HTML output, useful for firefox/firebug debugging etc)

Requirements

Currently, Jasmine Junit Runner relies on Rhino 1.7R2 (+ es5-shim) & Envjs 1.2 to interpret JS code. It also uses Jamsine 1.0.2 to read your spec files. All js libs are located in test/javascript/lib .

Dependencies Overview

See the pom.xml (Maven2) - you can build the whole thing using:

mvn clean install

  • Rhino 1.7R2 + es5-shim 0.0.4 (not needed if you'll be using 1.7R3)
  • Envjs 1.2 + required hacks in env.utils.js
  • Jasmine 1.0.2
  • Java libs: commons-io and commons-lang (test libs: mockito and fest assert)

Examples

Running a spec file as a Junit test

Use the default spec naming convention

If you do not specify specs with the annotation, the runner will auto-pick the spec name using your test class. The below test will load myAwesomeSpec.js from the specs dir (jsRootDir + '/specs/').

@RunWith(JasmineTestRunner.class)
@JasmineSuite(sources = { 'jQuery.js', 'myAwesomeCode.js' } )
public class MyAwesomeTest {
}

your awesome production code relies on jQuery (of course it does), so you'll have to include it.

Your spec file might look like this:

describe("my awesome code", function() {
	it("will always run", function() {
		expect(stuff.DoCoolThings()).toBe("awesome");
	});
});

Using Junit's @Before and @After

It's possible to do some extra work before and after each spec run:

@RunWith(JasmineTestRunner.class)
@JasmineSuite
public class MyAwesomeTest {

  @Before
  public void beforeStuff(RhinoContext context) {
    context.evalJS("var prefabVar = { cool: 'yeah!' };");
  }
  
  @Before
  public void beforeStuffNoContext() {
    System.out.println("I'm gonna blow! Or Will I?");
  }
  
  @After
  public void afterStuff() {
    // say cool things
  }

}

What's happening?

  • You can define n number of PUBLIC methods annotated with @Before or @After
  • You can, but don't have to, take the RhinoContext object as the only parameter. This allows you to set stuff up in JS space before running the spec.

Generating a spec runner

Your awesome test (example 1) would for instance generate this html file:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
	<head>
		<title>Jasmine Test Runner</title>
		<link rel="stylesheet" type="text/css" href="./../lib/jasmine-1.0.2/jasmine.css">
		<script type="text/javascript" src="./../lib/jasmine-1.0.2/jasmine.js"></script>
		<script type="text/javascript" src="./../lib/jasmine-1.0.2/jasmine-html.js"></script>
		
		<script type='text/javascript' src='./../../../main/webapp/js/jquery.js'></script>
		<script type='text/javascript' src='./../../../main/webapp/js/myawesomecode.js'></script>
		
		<script type='text/javascript' src='./../specs/myawesomespec.js'></script>
	</head>
	<body>

		<script type="text/javascript">		
			jasmine.getEnv().addReporter(new jasmine.TrivialReporter());
			jasmine.getEnv().execute();
		</script>
	</body>
</html>

You can inspect the output using firefox, or debug in your spec file using firebug.

Debugging in Java

When the debug mode flag has been set to true, you can use the Rhino Debugger to set breakpoints. After pressing "GO", the tests will run and you can inspect stuff and step through the code.

Integrated debugging into for example Eclipse does not work for the moment.


Advanced: Implementation details

RhinoContext API

The RhinoContext class is basically a wrapper/facade/whatever which allows you to easily manipulate the Javascript scope. Read Rhino docs: scopes and contexts first please!

Creating a new RhinoContext initializes one "root" scope (toplevel), and assignes one context to the current Thread.

Evaluating async javascript code

Creating another RhinoContext while passing the root scope, uses prototypal inheritance to create a new toplevel scope. This means the root scope is shared across different contexts (and thus different threads). You can execute the runAsync method, which does this:

  • create a new thread and thus a new context
  • create a new scope based on the root one -> shared
  • execute stuff in the new scope (You can access root JS functions but not modify them, remember prototypal inheritance!)
  • cleanup

For example, JasmineSpec uses the execute Jasmine JS function on a spec and calls it in another thread:

        baseContext.runAsync(new RhinoRunnable() {

            @Override
            public void run(RhinoContext context) {
			    // get some random spec from Jasmine
			    NativeObject someSpec = (NativeObject) context.evalJS("jasmine.getEnv().currentRunner().suites()[0].specs()[0]");
                context.executeFunction(someSpec, "execute");
            }
        });

Creating a Rhino debugger

Basically creates a org.mozilla.javascript.tools.debugger.Main object. Pitfall: create before loading all required JS files, but after creating the rhino context! To acutally break once (so users can set breakpoints and press GO), use this:

debugger.doBreak();

Executing functions

executeFunction is a convenience method to call a function on a passed NativeObject. The function pointer may reside in the object's prototype, you don't need to explicitly check this in Javascript but you do using Rhino!

Envjs Utils/Hacks

Error.stack fix

In firefox, you can get a stacktrace from a JS exception using:

new Error("BOOM").stack

Of course this does not work in Envjs. But Rhino attaches an internal rhinoException to each JS Error object, so using a bit of magic, now it's possible to call getStackTrace()

Envjs.uri Windows relative paths fix

Use file:/// (three forward slashes) if no context has been provided. Works like this:

Envjs.uri(path, "file:///" + ("" + Envjs.getcwd()).replace(/\\/g, '/') + "/")

window.setTimeout fix

Used by Jasmine internally for async spec execution, but for some reason the Envjs Javascript implementation is broken. A simple fix is possible, since using Rhino you can call Java objects in Javascript space! Wow awesome. So just create a new thread and use sleep:

	window.setTimeout = function(closure, timeout) {
		spawn(function() {
			java.lang.Thread.sleep(timeout);
			closure();
		});
	};