BDD with FlexUnit using workarounds

We will now improve on the methodology introduced in the previous post.

The source code for this approach is available here.

Make sure to add “-includes org.mockito.integrations.flexunit4.MockitoClassRunner” to the compiler arguments when compiling the test application.

Running specs inside private classes

Most of you will already know that it is indeed possible to include more than one class inside one ActionScript file.

The crux is that only one of them can be made public. The other ones need to be private and outside of any package. They are consequently only visible to classes within that same ActionScript file.

For that reason the FlexUnit test runner has no way of finding them and thus cannot run the contained specifications.

Here is what we need to do:

  1. find a way to make the private classes accessible outside their containing ActionScript file
  2. tell the FlexUnit test runner to run the specs inside them – this will require the creation of a custom runner

The first part is rather simple. All we need is a static property on our public class that returns an Array of the private classes contained inside the same ActionScript file.

To show how this works, here is the new and improved EmployeeSpecs class and some of the private classes contained within the same file:
Don’t worry about the mockito related code at this point as it is explained in a separate post.


package bddwithmockito.specs.accounting.company
{
	import accounting.company.Employee;
	import accounting.interfaces.IBankAccount;

	import org.mockito.integrations.mock;

	import specutils.base.SpecificationsBase;

	public class EmployeeSpecs extends SpecificationsBase
	{
		public static function get contexts() : Array
		{
			return [
				initially,
				when_told_that_he_is_fired,
				when_bankaccount_is_connected_and_his_salary_is_1_dollar_paying_him,
				when_bankaccount_is_connected_and_his_salary_is_2_dollars_paying_him,
				when_bankaccount_is_not_connected_paying_him,
				];
		}

		protected var _bankAccount_Mock : IBankAccount;

		protected var _sut : Employee;

		override protected function context() : void
		{
			_bankAccount_Mock = mock(IBankAccount);

			_sut = new Employee(_bankAccount_Mock);
		}
	}
}
import bddwithmockito.specs.accounting.company.EmployeeSpecs;

import org.mockito.integrations.*;

class initially extends EmployeeSpecs
{

	[Test]
	public function should_not_have_been_paid() : void
	{
		the(_sut.wasPaid).shouldBeFalse;
	}

	[Test]
	public function should_not_have_been_fired() : void
	{
		the(_sut.wasFired).shouldBeFalse;
	}

	[Test]
	public function should_have_zero_salary() : void
	{
		the(_sut.salary).shouldEqual(0);
	}
}

class when_told_that_he_is_fired extends EmployeeSpecs
{
	override protected function because() : void
	{
		_sut.fire();
	}

	[Test]
	public function was_fired_should_return_true() : void
	{
		the(_sut.wasFired).shouldBeTrue;
	}
}

class Ctx_BankAccountIsConnected extends EmployeeSpecs
{
	override protected function context() : void
	{
		super.context();
		given(_bankAccount_Mock.isConnected).willReturn(true);
	}
}

class when_bankaccount_is_connected_and_his_salary_is_1_dollar_paying_him extends Ctx_BankAccountIsConnected
{
	private const SALARY : int = 1;

	override protected function context() : void
	{
		super.context();
		_sut.salary = SALARY;
	}

	override protected function because() : void
	{
		_sut.pay();
	}

	[Test]
	public function sets_wasPaid_to_true() : void
	{
		the(_sut.wasPaid).shouldBeTrue;

	}

	[Test]
	public function should_update_his_bank_account() : void
	{
		verify().that(_bankAccount_Mock.updateWithAmount(any()));
	}

	[Test]
	public function should_update_his_bankaccount_with_1_dollar() : void
	{
		verify().that(_bankAccount_Mock.updateWithAmount(SALARY));
	}
}

The function public static function get contexts() : Array passes all specification classes to the outside world.

I called it contexts() to indicate that it actually returns an Array of classes, each containing a context definition and related specifications.

Calling it specifications() would not have been reflective of that fact.

It is important to note, that the class names contain information about the context and the action since now there are no more extra packages to convey part of this information.

Finally we need to find a way to tell FlexUnit to run the tests contained inside each class.

Creating a custom FlexUnit Test Runner

This task is actually easier than it seems. Still I only want to do it once, so I created a ContextRunner class.

It has a run method that takes an Array of context class Arrays (quite a mouthful) and an optional Array of dependencies that need to be prepared by mockito (we’ll get to that in the next post).

The run method collects the individual specifications, sorts them and finally passes them to the runContexts() method.

This method pushes those specifications into a FlexUntit Test runner and instructs it to run.


        private function runContexts() : void
	{
		var testRunner : FlexUnitTestRunnerUI = new FlexUnitTestRunnerUI();
		testRunner.runWithFlexUnit4Runner(allContexts, "");
	}

Note:

The second argument of the runWithFlexUnit4Runner() method is ideally the test project name.

This name will change for each test project though and passing in some name will throw an error after the test run completes  if it doesn’t match the name of the project that contains the tests.

Just passing an empty string avoids this annoyance.

You can to take a closer look at the ContextRunner which is contained inside the SpecUtils project.

Creating an Air test application

Since we have to use our custom test runner, we can no longer use the integrated “Execute FlexUnit tests” functionality as this will just kick off the default FlexUnit test runner, which will fail to find any of our tests.

Instead we transform our application itself into a test runner.

The steps are simple:

  1. Create an Air Application
  2. Inside the onCreation Event instantiate the custom ContextsRunner
  3. pass it our private context classes that we obtain via the static contexts properties from our Specs classes
  4. optionally pass any dependencies that need to be prepared by mockito
  5. run in debug mode (F11)

I chose to use an Air application to avoid a tab to be opened inside my browser that presents me with this:

The presented information it is not very helpful in itself and only gets in the way of  looking at the detailed information inside the FlexUnit Results tab.

One solution is to close all tabs on a browser which will cause the Flash Player to close the popup automatically, but as soon as I have a browser instance open anywhere, it brings it into view, adds its tab and leaves it open.

By using an  Air application to run my tests, I can set the window visibility to false and am never bothered by anything popping up.

Here is the <WindowedApplication>  file for our test application.


<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
					   xmlns:s="library://ns.adobe.com/flex/spark"
					   xmlns:mx="library://ns.adobe.com/flex/mx"
					   visible="false"
					   creationComplete="onCreationComplete()">
	<fx:Script>
		<![CDATA[
			import accounting.interfaces.IBankAccount;
			import accounting.interfaces.IEmployee;

			import bddwithmockito.specs.accounting.company.EmployeeSpecs;
			import bddwithmockito.specs.accounting.company.EmployerSpecs;

			import mx.controls.Alert;

			import specutils.runners.ContextsRunner;

			private function get fakes() : Array
			{
				return [IBankAccount, IEmployee];
			}

			private function get allContexts() : Array
			{
				return [
					EmployeeSpecs.contexts,
					EmployerSpecs.contexts,
					];
			}

			private function onCreationComplete() : void
			{
				new ContextsRunner().run(allContexts, fakes);
			}
		]]>
	</fx:Script>

</s:WindowedApplication>

Referencing the FlexUnit libraries

When executing flex unit tests as intended by the FlashBuilder IDE, it will automatically add the FlexUnit libraries for you.

When we are using the just described approach instead, we can either add them manually, or trick FlashBuilder into doing it for us.

Just select “Execute FlexUnit Tests” from the menu. At that point the libraries will be added. In case FlashBuilder complains that there are no tests in our project, just add a dummy test inside any public class and try again.

After the libraries have been added, we delete the FlexUnit application that was created and the dummy test case (if we needed one).

Now we are ready to use the FlexUnit libraries in our test classes.

Note:

If we are using the SpecUtils library classes to run our tests and make our assertions, we don’t need to includethe FlexUnit libraries, since then there is no need to directly reference the classes contained in them.

What’s the catch?

There are actually two drawbacks to doing things as described.

  • Since we manually collect and run our specifications, we need to make sure to pass the containing classes out through our context property
    • if we forget to do this, the test runner will do just fine, but our newly added specs inside it are never executed
    • if we are following the Red-Green-Refactor process, this shouldn’t  be a problem, as we will notice that nothing failed, after we added another failing test
    • this will remind us to include the new context inside the Array that we pass out to the runner
  • The other drawback is due to the fact that the FlexUnitResult navigation does not work for private classes
    • clicking on a test inside the FlexUnit Results tab will produce an error message instead of navigating to the test
    • it’s no biggie though, since all we have to do is to open the main Specs class, look at the classes being passed out, find the one containing our test and press F3 to navigate to it

Summary

Advantages

  • As Before:
    • ability to convey lots of information without verbose test names
    • contexts are built up incrementally and are centralized which makes for easier maintenance
    • specific/general contexts clearly communicate what is needed for individual/all specifications
    • actions and assertions are clearly separated into because() and [Test] methods
    • specifications are easy to read as they are one liners
  • no more file/package explosion
  • easier to navigate and thus easier to understand and follow for people not familiar with the concept

Disadvantages

  • does not integrate fully with the FlexUnit framework and thus requires the creation of custom test runners
  • automatic navigation from test results to the related test code does not work

In the next post we’ll look at a way to make mockito part of our workflow in a way that works great with the described approach.

Advertisements
  1. Leave a comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: