My team wanted to use Gradle to build our Grails project. We had some trouble with version compatibility problems between Grails and the Grails build plugin, though. Instead of using the plugin, we tried writing some code that called the ‘grails’ command directly, but that caused problems with the CI server because it required that the correct version of Grails be preinstalled on all build servers and exist in a known location. I decided to try my hand at writing a simpler Grails build plugin and this is what I came up with. It doesn’t require Grails to be pre-installed on the machine and, since it just executes the ‘grails’ command, it works with any version.
Here’s how to add commands to Gedit to format JSON and XML documents.
- Ensure that you have an up-to-date version of Python. It’s included with nearly every Linux distribution.
- Ensure that the External Tools plugin is installed
- Click Edit -> Preferences
- Select the Plugins tab
- Check the box next to External Tools
- Click Close
- Add the Format JSON command
- Click Tools -> Manage External Tools…
- Click New (bottom left, looks like a piece of paper with a plus sign)
- Enter a name (Format JSON)
- Paste this text into the text window on the right
#! /usr/bin/env python import json import sys j = json.load(sys.stdin) print json.dumps(j, sort_keys=True, indent=2)
- Set Input to Current document
- Set Output to Replace current document
- Add the Format XML command
- Install lxml (on Ubuntu, sudo apt-get install python-lxml)
- Python’s included XML modules either don’t support pretty printing or are buggy
- Create a new external tool configuration as above (Format XML)
- Paste this text into the text window on the right
#! /usr/bin/env python import sys import lxml.etree as etree import traceback result = '' for line in sys.stdin: result += line try: x = etree.fromstring(result) result = etree.tostring(x, pretty_print=True, xml_declaration=True, encoding=”UTF-8″) except: etype, evalue, etraceback = sys.exc_info() traceback.print_exception(etype, evalue, etraceback, file=sys.stderr) print result
- Set Input to Current document
- Set Output to Replace current document
- Install lxml (on Ubuntu, sudo apt-get install python-lxml)
Thanks to Diego Alcorta for improvements that preserve XML declarations and set encoding!
I recently attended a presentation by Tim Berglund about Gaelyk at Uberconf. The technology of Gaelyk is pretty straightforward and doesn’t need much of an introduction, especially with the excellent tutorial available on its web site. Watching the presentation, I was struck by how little there is to Gaelyk and wondered why I was there. Sitting in a room of developers and coding a working, useful application in just over an hour, though, reminded me about how much fun web programming can be. Many of us have become so accustomed to frameworks like Struts, WebMVC, GWT and Grails that we’ve forgotten the simpler times, when you could slap together some Servlets and JSPs and have fun putting together a small, useful app. Gaelyk brings back those simpler times and makes them even more enjoyable by adding the conveniences of Groovy and App Engine.
I picked an app that I wanted to code, something that would tell Yammer users what their companies are talking about. It’s available for anyone to use at http://whatyammer.appspot.com.
Data access
Want to save a record to the database? Forget about messing around with bean and data access classes, database connections and ORM. No need to create tables. You’re coding for fun!
def user = new Entity('user')
user.email = params.email
user.name = params.name
user.phone = params.phone
user.save()
That’s all. To get that record back out
datastore.execute {
select single from user
where email == params.email
}
Forwarding objects to a view
Gaelyk views are coded in gtpl files, which are much like JSPs. The controller, echo.groovy
request.users = datastore.execute { select all from user where email == params.email }
forward 'showusers.gtpl'
Adventure Time with <%= (request.users*.name).join(' and ') %>
* Update February 13th, 2012: Thanks to Ben Ripkens for updates to match the new Gradle API.
While searching online, I found many suggestions for how to add a new test target to a Gradle script. Most of them were wrong and others didn’t properly separate the integration test target from standard targets. After not finding a solution, I came up with one on my own.
This example sets up integration tests for Groovy .
Create a source set
This will separate the integration test code from other code, allowing it to be built separately.
- The classpath in the example gives integration tests access to all application and test classes
- The source location will be src/integrationTest/groovy
sourceSets {
integrationTest {
compileClasspath = sourceSets.main.output + configurations.testRuntime
runtimeClasspath = output + sourceSets.main.output + configurations.testRuntime
groovy {
srcDir 'src/integrationTest/groovy'
}
}
}
Add the target
task integrationTest(type: Test) {
testClassesDir = sourceSets.integrationTest.output.classesDir
classpath = sourceSets.integrationTest.runtimeClasspath
}
Occasionally, I see issues pop up on Java projects about builds not working properly. They usually look like, “The build worked yesterday, but it doesn’t work today. I was working on it, but I didn’t change anything related to the part that is failing.” or “I can’t figure out why, when I run this by itself, it passes. When I run it as a part of a full build, it fails.” Build errors are almost always caused by a misunderstanding of the function of “depends”.
How Not to Use Depends
This probably works, but only by chance.
<target name="test" depends="clean,compile,compile-test,test,service-test" />
Depends is not a list of tasks to be executed. It’s a list of dependencies that must be satisfied before a target can be completed. Execution is not guaranteed and side effects are common. Take a close look at what each of the tasks in the following script is doing.
<target name="functional-test" depends="start-server,run-tests,stop-server" />
<target name="service-test" depends="start-server,run-service-tests,stop-server" />
<target name="test" depends="clean,compile,compile-test,functional-test,service-test" />
To a human, the intent is obvious.
- Clean and compile
- Start server
- Run functional tests
- Stop server
- Start server
- Run service tests
- Stop server
But Ant will not execute those tasks. Since they were declared as dependencies, Ant evaluates them as such. The functional tests will run as you would expect. When it runs the service-test target, though, it will see the start and stop server tasks again. As far as it’s concerned, those dependencies have already been satisfied, so it will skip start-server and stop-server. Ant is evaluating the script correctly, but the script is wrong.
Dependencies are not tasks to be executed. They are dependencies. Once a dependency has been satisfied, it will not be executed again.
Antcall Is Not Evil
It exists for a reason. Use it when it is useful. Dependencies are dependencies, not task lists. In this case, it would be impractical to try to write the script to use depends attributes to cause events to happen correctly. It can be fixed very easily by using antcall. Each antcall is evaluated separately, so you don’t have to worry about side effects like the script above. It’s also easier to read. You can see what’s intended to happen before this target and you can see what this target is intended to do.
<target name="functional-test" depends="start-server,run-tests,stop-server" />
<target name="service-test" depends="start-server,run-service-tests,stop-server" />
<target name="test" depends="clean,compile,compile-test">
<antcall target="functional-test" />
<antcall target="service-test" />
</target>
Use Your Own Judgement
Obviously, antcall should not be used instead of depends. Depends is far more useful. But don’t throw a tool like antcall away just because it doesn’t seem as cool or concise or elegant.
After hearing about Go and its love of parallelism, I wanted to give it a try. I couldn’t think of any small projects that needed to be threaded until recently. My employer uses an RPM system, but I’m using Ubuntu, which requires deb files. Searching through the RPMs in a browser is a pain, so I wanted a small script that would search for me. Here’s the script with server names changed.
package main import ( "bufio" "fmt" "http" "os" "regexp" "strings" ) var servers = []string { "http://www.google.com/search?client=ubuntu&channel=fs&q=go+language&ie=utf-8&oe=utf-8", "http://golang.org/", "http://golang.org/doc/go_tutorial.html"} var maxOpenRequests int = 3 var rpmRegexp = regexp.MustCompile("href=\"[^\"]+\"") func main() { search := getSearchTerm() out := make(chan string) done := make(chan int) go printer(out) for _, url := range servers { go searchURL(search, url, out, done) } for i := 0; i < len(servers); i++ { <- done } } func getSearchTerm() string { args := os.Args if len(args) != 2 { die("Please enter one search term") } return args[1] } func die(message string) { fmt.Println(message) os.Exit(1) } func printer(out chan string) { for { fmt.Println(<- out) } } var requestSemaphore = make(chan int, maxOpenRequests) // Integer chanel with a maximum queue size func searchURL(search string, url string, out chan string, done chan int) { requestSemaphore <- 1 // Block until put in the semaphore queue response, realURL, err := http.Get(url) if err == nil { bufferedReader := bufio.NewReader(response.Body) err = searchAll(search, bufferedReader, url, out) response.Body.Close() } if err != nil { die("Could not read from " + realURL + ":" + err.String()) } <- requestSemaphore // Dequeue from the semaphore done <- 1 // Signal that function is done } func searchAll(search string, reader *bufio.Reader, httpRoot string, out chan string) (os.Error) { var error os.Error = nil; for { var line, err = reader.ReadString('\n') if err != nil { if err == os.EOF { break } return error } var hrefs = rpmRegexp.AllMatchesStringIter(line, 0) for href := range hrefs { start := strings.Index(href, "\"") + 1 end := len(href) - 1 packageFile := href[start : end] if strings.Index(packageFile, search) != -1 { out <- httpRoot + packageFile } } } return error }
A quick overview
Go uses “channels” for communication among threads.
- The main thread creates two channels, one to receive search output and one to receive “done” responses when a child thread finishes.
- The main thread attaches the output channel to a simple function that prints to standard out.
- The main thread spawns one child thread per server, giving each the output and done channels.
- The main thread loops over the done channel once for each server, waiting until all requests are complete.
- Each child blocks immediately until space is available in a semaphore.
- The child performs the HTTP get, then searches the response for anything matching a regular expression. Any matches are signaled back to the output channel.
- The child opens a space in the semaphore and signals to the main thread that it is done.
There isn’t much more to say. The Go web site has great tutorials, so I don’t yet feel the need to write one.
By the way, if you’re searching for Go resources, be sure to search for “go language” and not “go”. But then, that’s probably how you found this page 😉
import (
“bufio”
“fmt”
“http”
“os”
“regexp”
“strings”
)
var servers = []string {
“http://apt.duncllc.com/dist/WS4.0/RedHat/RPMS/”,
“http://apt.duncllc.com/dist/WS4.0/RedHat/RPMS.extras/”,
“http://apt.duncllc.com/dist/WS4.0/RedHat/RPMS.java/”}
var maxOpenRequests int = 3
var rpmRegexp = regexp.MustCompile(“href=\”[^\”]+\””)
func main() {
search := getSearchTerm()
out := make(chan string)
done := make(chan int)
go printer(out)
for _, url := range servers {
go searchURL(search, url, out, done)
}
for i := 0; i < len(servers); i++ {
<- done
}
}
func getSearchTerm() string {
args := os.Args
if len(args) != 2 {
die(“Please enter one search term”)
}
return args[1]
}
func die(message string) {
fmt.Println(message)
os.Exit(1)
}
func printer(out chan string) {
for {
fmt.Println(<- out)
}
}
var requestSemaphore = make(chan int, maxOpenRequests) // Integer chanel with a maximum queue size
func searchURL(search string, url string, out chan string, done chan int) {
requestSemaphore <- 1 // Block until put in the semaphore queue
response, realURL, err := http.Get(url)
if err == nil {
bufferedReader := bufio.NewReader(response.Body)
err = searchAll(search, bufferedReader, url, out)
response.Body.Close()
}
if err != nil {
die(“Could not read from ” + realURL + “:” + err.String())
}
<- requestSemaphore // Dequeue from the semaphore
done <- 1 // Signal that function is done
}
func searchAll(search string, reader *bufio.Reader, httpRoot string, out chan string) (os.Error) {
var error os.Error = nil;
for {
var line, err = reader.ReadString(‘\n’)
if err != nil {
if err == os.EOF {
break
}
return error
}
var hrefs = rpmRegexp.AllMatchesStringIter(line, 0)
for href := range hrefs {
start := strings.Index(href, “\””) + 1
end := len(href) – 1
packageFile := href[start : end]
if strings.Index(packageFile, search) != -1 {
out <- httpRoot + packageFile
}
}
}
return error
}
Python is a language that some people like and use. I recently was given a small Python application and had to refactor it so that I could implement unit and functional tests. It took me a while to find the resources and learn how to implement them, so I thought I’d write it up here.
* Please note that this is not a unit testing tutorial. Those are plentiful and can be found elsewhere. This describes setting up a professional Python project with a real package structure, injection and tests.
Python Project Structure
Python was conceived as a scripting language. Unlike compiled languages like C or Java, Python files are designed to be directly runnable.
RunMe.java -p someargument
< Not ok!RunMe.py -p someargument
< Ok!
This is convenient for small scripts, but doesn’t help applications. Since you are intended to run .py
files directly, Python projects should not have source (src
) or binary (bin
) folders. Having a source folder makes a program difficult to run. The root of the Python package structure should be the root of the project.
For the same reason, the test
folder should not be separate. It should be a package in the main project. Since the root folder is the root package, you won’t have much choice.
Example
MyProject
__init__.py
model
__init__.py
user.py
service
__init__.py
user_service.py
ldap_user_service.py
test
__init__.py
unit
__init__.py
service
__init__.py
user_service_test.py
functional
__init__.py
service
__init__.py
user_service_test.py
If you’re new to Python, please note that those __init__.py
s are required. They tell Python which folders contain Python files. This is Python’s way of allowing you to have other folders, such as config, that don’t contain source files.
Note
You should specify exactly one class per file. It makes your code easier to find and read. |
Injection with snake-guice
Injection is essential for unit tests. In the example above, LDAPUserService
may connect to an LDAP server to retrieve information, something we don’t want to happen in unit tests.
I’ve been using snake-guice and highly recommend it.
Install snake-guice, Mock and nose
If you don’t have it, grab a copy of easy install. Then, $ sudo easy_install.py snake-guice Mock nose
. That installs packages from PyPI, the Python Package Index. For those used to non-scripting languages, this may not seem like a great way to use external libraries. Since Python is a scripting language, however, it’s the only way that makes sense.
Create a module and use it to instantiate your application
class ExampleModule:
def configure(self, binder)
binder.bind(UserService, to=LDAPUserService)
class ExampleRunner:
user_service = None
@inject(user_service=UserService)
def __init__(self, user_service):
self.user_service = user_service
def main(user_service)
self.user_service.login("name", "password")
injector = Injector(ExampleModule)
runner = injector.get_instance(ExampleRunner)
When get_instance
is called, injector
will use the ExampleModule
to discover bindings. Then, it will create an ExampleRunner
, passing in arguments specified by @inject
.
The setUp
method of PyUnit tests should create an injector with a TestExampleModule
, which should be configured to return mocks.
class TestExampleModule:
def user_service = Mock()
def configure(self, binder)
binder.bind(UserService, to=user_service)
class ExampleTest(TestCase):
application = None
def setUp(self):
Injector = Injector(TestExampleModule)
self.application = injector.get_instance(ExampleApplication)
def test_something(self):
application.user_service.search.return_value = "expected"
actual = application.user_service.search("any")
self.assertEquals(expected, actual)
Since setUp
is called before each test, each test method gets a fresh mock that it can configure any way it likes.
Similarly, a functional test module can be created that connects to a local or testing server.
Mocking with Mock
I recommend using Mock for mocking, which is why I had you install it earlier. Be careful when searching for it because there appear to be two projects called Mock.
The Mock package is well documented. Please go to the Mock site for more information.
Running Tests
For running tests, I recommend Nose. To run your tests, open a command line and change to your root project folder. Then, nosetests. Nose will take care of adding the packages you installed earlier, snake-guice and Mock, to the Python path. It will also find all tests, run them and report the results. Handy!
$ cd ~/exampleproject
$ nosetests
To run only unit or functional tests, use the -w argument.
$ nosetests -w ./ ./test/unit
$ nosetests -w ./ ./test/functional
Is that all?
To those who have not used injection or mocks for testing, this may seem like a lot of work. I hope you’ll try it, though, because it’s a one-time setup. Once these good practices are in place, you’ll find that all of your code organization and testing becomes simple and almost automatic. If I missed something or if you have any suggestions, please post below.
In a previous post (http://www.connorgarvey.com/blog/?p=132), I wrote about how to use Guice injection for Flex services. I used web.xml to configure the MessageBrokerServlet and configured each Flex service to use Guice as a factory. Since then, on this project, we’ve had to introduce new servlets. Rather than continue to use web.xml’s verbose and fully-qualified-path based configuration, we moved to using Guice’s ServletModule class. Here are the steps we followed.
- Ensure the guice-servlet.jar is included in your project and is deployed with your build.
- Add the Guice filter to web.xml.
<filter> <filter-name>guiceFilter</filter-name> <filter-class>com.google.inject.servlet.GuiceFilter</filter-class> </filter> <filter-mapping> <filter-name>guiceFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
- Create a servlet module, a class extending com.google.inject.servlet.ServletModule.
- Override the configureServlets() method of ServletModule and add the message broker servlet configuration.
this.bind(MessageBrokerServlet.class).in(Scopes.SINGLETON); final Map<String, String> params = new TreeMap<String, String>(); params.put("services.configuration.file", this.context .getRealPath("WEB-INF/config/flex/services-config.xml")); this.serve("/messagebroker/*").with(MessageBrokerServlet.class, params);
- Normally, servlets configured in Guice are tagged with @Singleton. Since the MessageBrokerServlet is third party, it’s marked as a singleton here, in the module.
- Add the new module to the Guice servlet context listener, which should already be configured in web.xml.
- Remove the servlet and servlet-mapping from web.xml.
I just finished putting a server together that uses Google’s Guice and Adobe’s BlazeDS. Here’s how you can quickly get a project working using the same configuration.
* Update: A new post describes using Guice’s ServletModule to configure the MessageBrokerServlet.
Download the libraries
Download Guice and BlazeDS. Use the project configured in the BlazeDS war to get started.
Create the Guice factory
Since BlazeDS provides the servlet, you won’t be able to configure your classes using Guice unless you have a Flex factory. This code will inject itself with Guice to get instances of Flex services.
package com.connorgarvey.guiceblazeds.servlet;
import java.util.HashMap;
import java.util.Map;
import com.google.inject.Injector;
import flex.messaging.FactoryInstance;
import flex.messaging.FlexContext;
import flex.messaging.FlexFactory;
import flex.messaging.config.ConfigMap;
import flex.messaging.services.ServiceException;
/**
* <p>A Flex factory that retrieves instances of Flex services from Guice</p>
* <p>This is based on a similar factory built for Spring by Jeff Vroom</p>
* @author Connor Garvey
* @created May 27, 2009 1:09:49 PM
* @version 1.0.0
* @since 1.0.0
*/
public class GuiceFactory implements FlexFactory {
private static final String SOURCE = "source";
/**
* @see flex.messaging.FlexFactory#createFactoryInstance(java.lang.String, flex.messaging.config.ConfigMap)
*/
public FactoryInstance createFactoryInstance(final String id, final ConfigMap properties) {
final GuiceFactoryInstance instance = new GuiceFactoryInstance(this, id, properties);
instance.setSource(properties.getPropertyAsString(SOURCE, instance.getId()));
return instance;
}
/**
* @see flex.messaging.FlexConfigurable#initialize(java.lang.String, flex.messaging.config.ConfigMap)
*/
public void initialize(final String id, final ConfigMap configMap) {
}
/**
* @see flex.messaging.FlexFactory#lookup(flex.messaging.FactoryInstance)
*/
public Object lookup(final FactoryInstance inst) {
return inst.lookup();
}
static class GuiceFactoryInstance extends FactoryInstance {
private Map<String, Class<?>> classes = new HashMap<String, Class<?>>();
GuiceFactoryInstance(final GuiceFactory factory, final String id, final ConfigMap properties) {
super(factory, id, properties);
}
@Override
public Object lookup() {
final Injector injector = (Injector)FlexContext.getServletContext().getAttribute(
GuiceServletContextListener.KEY);
injector.injectMembers(this);
String className = this.getSource();
Class<?> clazz = this.classes.get(className);
if (clazz == null) {
try {
clazz = Class.forName(this.getSource());
this.classes.put(className, clazz);
}
catch (ClassNotFoundException ex) {
ServiceException throwing = new ServiceException();
throwing.setMessage("Could not find remote service class '" + this.getSource() + "'");
throwing.setRootCause(ex);
throwing.setCode("Server.Processing");
throw throwing;
}
}
return injector.getInstance(clazz);
}
@Override
public String toString() {
return "Guice factory <id='" + this.getId() + "',source='" + this.getSource() +
"',scope='" + this.getScope() + "'>";
}
}
}
Create a context listener
The Guice servlet jar contains a ServletContextListener, but I haven’t taken the time to integrate it into this solution. For simplicity, you can use this one, which works with the factory above.
package com.connorgarvey.guiceblazeds.servlet;
import java.util.List;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Module;
/**
* Prepares Guice on application startup
* @author Connor Garvey
* @created May 27, 2009 8:37:26 AM
* @version 1.0.0
* @since 1.0.0
*/
public abstract class GuiceServletContextListener implements ServletContextListener {
/**
* The key of the servlet context attribute holding the injector
*/
public static final String KEY = Injector.class.getName();
/**
* @see javax.servlet.ServletContextListener#contextDestroyed(javax.servlet.ServletContextEvent)
*/
public void contextDestroyed(final ServletContextEvent servletContextEvent) {
servletContextEvent.getServletContext().removeAttribute(KEY);
}
/**
* @see javax.servlet.ServletContextListener#contextInitialized(javax.servlet.ServletContextEvent)
*/
public void contextInitialized(final ServletContextEvent servletContextEvent) {
servletContextEvent.getServletContext().setAttribute(KEY,
this.getInjector(servletContextEvent.getServletContext()));
}
private Injector getInjector(final ServletContext servletContext) {
return Guice.createInjector(this.getModules());
}
/**
* Gets the modules used by the application
* @return the modules
*/
protected abstract List<? extends Module> getModules();
}
Extend GuiceContextListener
Create a concret version of the class above. It should return all modules needed for the application.
Modify web.xml
Now, prepare web.xml with the normal BlazeDS settings plus some extras for Guice.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<display-name>Guice BlazeDS</display-name>
<description>Guice BlazeDS</description>
<listener>
<listener-class>flex.messaging.HttpFlexSession</listener-class>
</listener>
<listener>
<listener-class>com.connorgarvey.guiceblazeds.servlet.MyCustomGuiceServletContextListener</listener-class>
</listener>
<servlet>
<servlet-name>MessageBrokerServlet</servlet-name>
<display-name>MessageBrokerServlet</display-name>
<servlet-class>flex.messaging.MessageBrokerServlet</servlet-class>
<init-param>
<param-name>services.configuration.file</param-name>
<param-value>/WEB-INF/config/flex/services-config.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>MessageBrokerServlet</servlet-name>
<url-pattern>/messagebroker/*</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
</welcome-file-list>
</web-app>
This way, BlazeDS will start at server startup, followed by Guice.
Modify BlazeDS config
Open services-config.xml and add this at the top of the file. Point it to the Guice factory.
<?xml version="1.0" encoding="UTF-8"?>
<services-config>
<factories>
<factory id="guice" class="com.connorgarvey.guiceblazeds.servlet.GuiceFactory" />
</factories>
Set the factory of all remoting destinations
In remoting-config.xml, be sure to set the factory of all destinations to the name specified in services-config.xml above.
<destination id="SomeService"> <properties> <factory>guice</factory> <source>com.connorgarvey.guiceblazeds.service.flex.SomeFlexService</source> </properties> </destination>
How this all works
- When the server starts, BlazeDS and Guice are initialized by the servlet listeners.
- When BlazeDS receives a request, to a service, it’ll see that it’s supposed to retrieve it from the “guice” factory.
- The Guice factory will return an instance of the class specified in the source of the service configuration from Guice to BlazeDS for use in completing the request.
As described in an earlier post, Android is not friendly to mocking frameworks or mock-style testing. If you want to test any class in your application that deals with the Android API, it’s best to run your tests through the emulator, accessing real Android classes. It’s unfortunate because you’re not just testing your application. You’re also testing Android. Anyway, here’s a way to mock a UI component.
If you’re just starting, here are a couple notes to keep you on the right track.
- Android is bundled with JUnit 3. Don’t try using an updated JUnit library or another testing framework. The Android jar doesn’t contain any functional code, so all test cases have to be run in the emulator, which uses JUnit 3. The test framework that will work best is in the Android API.
- If you need basic implementations of Android classes, try to avoid mocking them.
“Mock”ing
In my latest test, I needed a TextView so that I could call the simplest method, setText(String). I’ll describe how I got one.
Don’t bother with the android.test.mock package. It just contains implementations of classes that throw UnsupportedOperationExceptions. There isn’t anything there that I have yet found useful.
- In the test case, instead of extending TestCase, extend at InstrumentationTestCase or, if necessary, one of its subclasses. It’ll set up most of the stuff that’s available in an Activity and make it available to your test case.
public class AnAndroidTest extends InstrumentationTestCase {
- Create a mock implementation of TextView or the class you need.
public class MockTextView extends TextView { public MockTextView(final Context context) { super(context); } }
- The TextView constructor needs a real Context object because it will call methods on it. The context is difficult to mock because parts of it are private to Android. Since the Android JAR is just an API and doesn’t have any functional code, you couldn’t even see the methods if you tried. They only exist in the VM in the emulator. AFAIK, if you can’t see a method, you can’t mock it. That’s why your test case extends InstrumentationTestCase. Put this in it.
final TextView textView = new MockTextView(this.getInstrumentation().getContext());
Now write the test case. The text view is real and has a fully functional context, so the emulator will have everything it needs to support your test case.