Following on from the other questions in my Android TDD series, I have managed to get as far as using Robolectric, Mockito, Maven and ABS to unit test my Android developments. Evidently, I am seemingly pushing my own knowledge boundaries but the Android CI dream is just too alluring. If you can assist with my next problem I would be very grateful, here it is;
I am wanting to write an integration test case which takes my application's database from v1 to head. I am using Dagger for DI and to facilitate this non-standard JUnit test I am injecting the class required to perform the upgrade into my test, as so;
#RunWith(RobolectricTestRunner.class)
public class TestDatabaseHelper {
private static final String V1_DATABASE_SQL = "res/test-support/v1-database/v1-database.sql";
#Inject private UpgradeAuditService upgradeAuditService;
#Rule public StoutLoggingRule loggingRule = new StoutLoggingRule();
#Rule public DaggerInjector injector = new DaggerInjector();
/**
* Tests upgrading the database from version 1 to HEAD.
*
* #throws NameNotFoundException When there is no app but it's running the app. A WTF moment.
* #throws IOException when something goes wrong.
*/
#Test
#Config(manifest="../OceanLife/AndroidManifest.xml")
public void testUpgradingToHead() throws NameNotFoundException, IOException {
// Given.
final Context testApplicationContext = Robolectric.getShadowApplication().getApplicationContext();
final SQLiteDatabase database = SQLiteDatabase.openDatabase("/data/data/com.oceanlife/databases/oceanlife.db", null, SQLiteDatabase.OPEN_READWRITE);
final int versionCode = testApplicationContext.getPackageManager().getPackageInfo("com.oceanlife", 0).versionCode;
// When.
replaceDatabase(database);
new DatabaseHelper(testApplicationContext, upgradeAuditService).onUpgrade(database, 1, versionCode);
// Then.
final int databaseVersion = SQLiteDatabase.openDatabase("/data/data/com.oceanlife/databases/oceanlife.db", null, SQLiteDatabase.OPEN_READWRITE).getVersion();
assertEquals("Database was not upgraded.", versionCode, databaseVersion);
}
Important points to note;
The test lives under it's own test project, separate to the project under test (see my linked questions for structure)
The failure (stack trace below) comes about when creating the real application's module graph.
The injector rule (described below) links to the test project Module which includes the module used when running my application for real.
What's happening under the DaggerInjector rule?
I attempt to inject any dependencies required by my test.
public class DaggerInjector implements MethodRule {
/**
* #see org.junit.rules.MethodRule#apply(org.junit.runners.model.Statement, org.junit.runners.model.FrameworkMethod, java.lang.Object)
*/
#Override
public Statement apply(final Statement base, final FrameworkMethod method, final Object target) {
return new Statement() {
#Override
public void evaluate() throws Throwable {
final ObjectGraph graph = ObjectGraph.create(new OceanLifeTestingModule());
graph.inject(target);
base.evaluate();
}
};
}
}
What does your testing module look like?
Adds entry points (this is dagger-0.9.1, not the latest where inject replaces entryPoints) for the tests and (to-be) test mocks replacements.
/**
* The injection container providing information on
* how to construct test support components.
*
* #author David C Branton
*/
#Module(includes = OceanLifeModule.class,
entryPoints = {FixtureBuilder.class,
TestSpotService.class,
TestDatabaseHelper.class},
complete = true,
overrides = true)
public class OceanLifeTestingModule {
/**
* Constructs this module.
*/
public OceanLifeTestingModule() { }
/**
* Provide the database.
*
* #return the application database.
*/
#Provides SQLiteDatabase provideDatabase() {
return SQLiteDatabase.openDatabase("/data/data/com.oceanlife/databases/oceanlife.db", null, SQLiteDatabase.OPEN_READWRITE);
}
}
My tests run green if I comment out the line of code responsible for creating my real application's object graph. Here is the stack trace;
Error 1: Caught and swallowed from my test perspective
dagger.internal.RuntimeAggregatingPlugin#getModuleAdapter
java.lang.RuntimeException: Unexpected failure loading com.oceanlife.OceanLifeModule$ModuleAdapter
Error 2: Dumped out into the console by Robolectric
java.lang.ArrayStoreException: sun.reflect.annotation.TypeNotPresentExceptionProxy
at sun.reflect.annotation.AnnotationParser.parseClassArray(AnnotationParser.java:653)
at sun.reflect.annotation.AnnotationParser.parseArray(AnnotationParser.java:460)
at sun.reflect.annotation.AnnotationParser.parseMemberValue(AnnotationParser.java:286)
at sun.reflect.annotation.AnnotationParser.parseAnnotation(AnnotationParser.java:222)
at sun.reflect.annotation.AnnotationParser.parseAnnotations2(AnnotationParser.java:69)
at sun.reflect.annotation.AnnotationParser.parseAnnotations(AnnotationParser.java:52)
at java.lang.Class.initAnnotationsIfNecessary(Class.java:3079)
at java.lang.Class.getAnnotation(Class.java:3038)
at dagger.internal.plugins.reflect.ReflectivePlugin.getModuleAdapter(ReflectivePlugin.java:51)
at dagger.internal.RuntimeAggregatingPlugin.getModuleAdapter(RuntimeAggregatingPlugin.java:98)
at dagger.internal.RuntimeAggregatingPlugin.getAllModuleAdapters(RuntimeAggregatingPlugin.java:55)
at dagger.ObjectGraph.makeGraph(ObjectGraph.java:115)
at dagger.ObjectGraph.create(ObjectGraph.java:103)
at com.oceanlife.MainApplication.onCreate(MainApplication.java:36)
at org.robolectric.internal.ParallelUniverse.setUpApplicationState(ParallelUniverse.java:146)
at org.robolectric.RobolectricTestRunner.setUpApplicationState(RobolectricTestRunner.java:387)
at org.robolectric.RobolectricTestRunner$2.evaluate(RobolectricTestRunner.java:227)
at org.junit.runners.BlockJUnit4ClassRunner.runNotIgnored(BlockJUnit4ClassRunner.java:79)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:71)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:49)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
at org.robolectric.RobolectricTestRunner$1.evaluate(RobolectricTestRunner.java:177)
at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
Investigation to-date
I've been focusing my efforts on why "Error 1" occurred. From reading the "Compile Time Code Generation" aspect to the user guide I can see that the adapter the RuntimeAggregatingPlugin is looking for (OceanLifeModule$ModuleAdapter) is generated at compile time. From that I've been inspecting my maven configuration and will be happy to provide that should the community feel it is necessary to figuring this out. The low level nature of "Error 2" makes me think it is more derivative than the root cause.
Ok. So this answer pointed me down the right track.
Essentially, this was owing to the fact that my test project didn't understand what the old v1 maps component com.google.android.maps.MapActivity etc. were. The way I resolved my problem was to import the maps.jar into my test project, i.e.;
<dependency>
<groupId>com.google.android.maps</groupId>
<artifactId>maps</artifactId>
<version>17_r3</version>
</dependency>
You can get hold of this via the Android SDK Deployer.
Related
Below is the code
#RunWith(AndroidJUnit4.class)
public class ScreenshotJavaTest {
// a handy JUnit rule that stores the method name
#Rule
public TestName nameRule = new TestName();
#Rule
public ActivityScenarioRule<MainActivity> activityScenarioRule =
new ActivityScenarioRule<>(MainActivity.class);
/**
* Captures and saves an image of the entire {#link MainActivity} contents.
*/
#Test
public void saveActivityBitmap() throws IOException {
writeToTestStorage(captureToBitmap(onView(isRoot())), getClass().getSimpleName() + "_" + nameRule.getMethodName());
}
/**
* Captures and saves an image of the 'Hello world' view.
*/
#Test
public void saveViewBitmap() throws IOException {
writeToTestStorage(captureToBitmap(onView(withText("Hello World!"))), getClass().getSimpleName() + "_" + nameRule.getMethodName());
}
/**
* Captures and saves an image of the entire device screen to storage.
*/
#Test
public void saveDeviceScreenBitmap() throws IOException {
writeToTestStorage(takeScreenshot(), getClass().getSimpleName() + "_" + nameRule.getMethodName());
}
}
All test cases are running but I am not able to find any screenshots anywhere in the device file manager. So How I will find those screenshots or there are some other ways we can achieve to get
these screenshots in automation testing
Cannot speak for real devices, but when using the Android Studio Simulator you can find the screenshots using the Device File Explorer at the following path:
/storage/emulated/0/googletest/test_outputfiles
Make sure to add the following line to your gradle file:
testInstrumentationRunnerArguments useTestStorageService: 'true'
I've been searching for hours and this doesn't seem to be documented anywhere.
I'm new to the Google App Engine, and I'm trying to make my first engine and connect it to my Android app. I have walked through this tutorial in order to learn about it:
https://cloud.google.com/endpoints/docs/frameworks/legacy/v1/java/helloendpoints-android-studio
I got it to work fine. I can access my app engine from my android app, and get the wanted response. The problem is, I want to restrict the endpoints of my API to my app's users only.
This is my API method (from the tutorial), and as for now, everyone can access my api's explorer and execute methods in it, as long as they are logged in to any Google account.
I want the users to be able to execute this method from my app only.
This is my app engine java file:
package com.example.Barda.myapplication.backend;
import com.google.api.server.spi.config.Api;
import com.google.api.server.spi.config.ApiMethod;
import com.google.api.server.spi.config.ApiNamespace;
import com.google.api.server.spi.response.UnauthorizedException;
import com.google.appengine.api.users.User;
import javax.inject.Named;
/**
* An endpoint class we are exposing
*/
#Api(
name = "myApi",
version = "v1",
clientIds = {Constants.ANDROID_CLIENT_ID},
audiences="firebase-wiki-race.appspot.com",
namespace = #ApiNamespace(
ownerDomain = "backend.myapplication.Barda.example.com",
ownerName = "backend.myapplication.Barda.example.com",
packagePath = ""
)
)
public class MyEndpoint {
/**
* A simple endpoint method that takes a name and says Hi back
*/
#ApiMethod(name = "sayHi")
public MyBean sayHi(#Named("name") String name) throws UnauthorizedException {
// if (user == null) throw new UnauthorizedException("User is Not Valid");
MyBean response = new MyBean();
response.setData("Hi, " + name);
return response;
}
}
This is constants class:
package com.example.Barda.myapplication.backend;
/**
* Contains the client IDs and scopes for allowed clients consuming your API.
*/
public class Constants {
public static final String ANDROID_CLIENT_ID = "*********************.apps.googleusercontent.com";
}
I have generated using my app's SH-1 and package name the ANDROID_CLIENT_ID.
I have searched online a lot, and read blogs and threads, but I couldn't make it work. Is this a possible thing to do? What am I doing wrong?
You'll want to follow the documentation's guide on adding authorization to the API backend. In this process you define a list of clients that are authorized to use your Endpoint.
Once that's done you can follow the guide on making authenticated calls from Android.
I've been attempting to develop an android application that can pull information from a CIFS / SMB protocol. I've tried JCIFS and keep getting errors with my eclipse application not being able to read the package (even after I've Javadoc'd it), and my last resort was to get help. Therefore, can anyone offer incite / information on how to do this?
I've tried this : Copy all files from server to Android Device
I've tried this : http://jcifs.samba.org/
I've tried this: http://learn-it-stuff.blogspot.com/2012/01/adding-jcifs-api-to-our-android-java.html
Maybe there is a couple things I'm not understanding at the moment that could be fleshed out.
My snippet of the code I'm using (copy and pasted directly from the third link for the most part) :
public void login(String address2, String username2, String password2)
throws Exception {
setDomain(UniAddress.getByName(address2));
setAuthentication(new NtlmPasswordAuthentication(address2, username2,
password2));
SmbSession.logon(getDomain(), authentication);
}
public UniAddress getDomain() {
return domain;
}
/**
* #param domain
* the domain to set
*/
public void setDomain(UniAddress domain) {
this.domain = domain;
}
/**
* #return the authentication
*/
public NtlmPasswordAuthentication getAuthentication() {
return authentication;
}
/**
* #param authentication
* the authentication to set
*/
public void setAuthentication(NtlmPasswordAuthentication authentication) {
this.authentication = authentication;
}
and then from here I'm calling the login(); with a address, username, and password. When I do this the application crashes and gives me an error of "NoClassDefFoundError" at the line: setDomain(UniAddress....), specifically from the UniAddress class.
Could anyone help me with this?
Noticing that you have followed the procedure from:
http://learn-it-stuff.blogspot.com/2012/01/adding-jcifs-api-to-our-android-java.html
in importing jcifs.jar for UniAddress and other jcifs apis,
but for it to work :
jcifs.jar must be present in <Project Name>/libs folder
I'm trying to run Jersey on Jetty on Android.
I've created an Android that instantiate a Jetty Server with a Jersey Servlet. Anyway when I start Jetty and visit a REST resource (in my case: http://192.168.1.12:8080/api/hello) I get a ContainerException with message: The ResourceConfig instance does not contain any root resource classes. (see exception stack trace below).
Any idea why?
MORE DETAILS:
Logcat is giving the following SEVER WARNINGS.
The following errors and warnings have been detected with resource and/or provider classes:
SEVERE: Missing dependency for field: private java.lang.ThreadLocal com.sun.jersey.server.impl.container.servlet.JSPTemplateProcessor.requestInvoker
SEVERE: Missing dependency for field: private java.lang.ThreadLocal com.sun.jersey.server.impl.container.servlet.JSPTemplateProcessor.requestInvoker
This is strange cause java.lang.ThreadLocal is available for Android and HttpServletRequest and HttpServletResponse should be available since I've included servlet-api-2.5.jar in the libs folder.
Jersey is dependent on some javax libraries (jaxb-api-2.2.2.jar,jndi-1.2.1.jar,stax-api-1.0-2.jar) which I had to add to project and set the --core-library parameter temporary to ignore dex warning about javax packages as dependencies.
I also removed the following classes (RenderedImageProvider,DataSourceProvider,MimeMultipartProvider from package com.sun.jersey.core.impl.provider.entity from jersey core jar) to avoid dependencies on java.awt and javax.mail.
EXCEPTION trace:
javax.servlet.UnavailableException: com.sun.jersey.api.container.ContainerException: The ResourceConfig instance does not contain any root resource classes.
at org.eclipse.jetty.servlet.ServletHolder.makeUnavailable(ServletHolder.java:409)
at org.eclipse.jetty.servlet.ServletHolder.initServlet(ServletHolder.java:450)
at org.eclipse.jetty.servlet.ServletHolder.getServlet(ServletHolder.java:331)
at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:511)
at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:476)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:119)
at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:517)
at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:226)
at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:935)
at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:404)
at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:184)
at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:870)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:117)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:116)
at org.eclipse.jetty.server.Server.handle(Server.java:346)
at org.eclipse.jetty.server.HttpConnection.handleRequest(HttpConnection.java:596)
at org.eclipse.jetty.server.HttpConnection$RequestHandler.headerComplete(HttpConnection.java:1051)
at org.eclipse.jetty.http.HttpParser.parseNext(HttpParser.java:592)
at org.eclipse.jetty.http.HttpParser.parseAvailable(HttpParser.java:214)
at org.eclipse.jetty.server.HttpConnection.handle(HttpConnection.java:426)
at org.eclipse.jetty.io.nio.SelectChannelEndPoint.handle(SelectChannelEndPoint.java:520)
at org.eclipse.jetty.io.nio.SelectChannelEndPoint$1.run(SelectChannelEndPoint.java:40)
at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:528)
at java.lang.Thread.run(Thread.java:1019)
Start Server Android Activity:
public class StartServerActivity extends Activity {
private Server webServer;
private final static String LOG_TAG = "Jetty";
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
System.setProperty("java.net.preferIPv4Stack", "true");
System.setProperty("java.net.preferIPv6Addresses", "false");
webServer = new Server(8080);
ServletHolder servletHolder = new ServletHolder(com.sun.jersey.spi.container.servlet.ServletContainer.class);
servletHolder.setInitParameter("com.sun.jersey.config.property.packages", "com.famenu.server.resources");
ServletContextHandler servletContextHandler = new ServletContextHandler(webServer, "/api", true, false);
servletContextHandler.addServlet(servletHolder, "/hello");
webServer.setHandler(servletContextHandler);
try {
webServer.start();
Log.d(LOG_TAG, "started Web server");
}
catch (Exception e) {
Log.d(LOG_TAG, "unexpected exception starting Web server: " + e);
}
}
}
Jersey Resource:
package com.famenu.server.resources;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
#Path("/")
public class HelloResource {
#GET
#Produces("text/plain")
public String getMsg() {
return "Hello Resource";
}
}
I'm using Jetty 7.3.0.v20110203 , Jersey 1.12 , Android 1.6
I arrived till this point after another exception explained here
don't use package/any other scanning on .. not supported platforms.
Classnames property: com.sun.jersey.config.property.classnames should work for you (you need to explicitly declare all your root resource (#Path annotated) classes).
In an Android app I have a utility class that I use to parse strings for 2 regEx's. I compile the 2 patterns in a static initializer so they only get compiled once, then activities can use the parsing methods statically.
This works fine except that the first time the class is accessed and loaded, and the static initializer compiles the pattern, the UI hangs for close to a MINUTE while it compiles the pattern! After the first time, it flies on all subsequent calls to parseString().
My regEx that I am using is rather large - 847 characters, but in a normal java webapp this is lightning fast. I am testing this so far only in the emulator with a 1.5 AVD.
Could this just be an emulator issue or is there some other reason that this pattern is taking so long to compile?
private static final String exp1 = "(insertratherlong---847character--regexhere)";
private static Pattern regex1 = null;
private static final String newLineAndTagsExp = "[<>\\s]";
private static Pattern regexNewLineAndTags = null;
static {
regex1 = Pattern.compile(exp1, Pattern.CASE_INSENSITIVE);
regexNewLineAndTags = Pattern.compile(newLineAndTagsExp);
}
public static String parseString(CharSequence inputStr) {
String replacementStr = "replaceMentText";
String resultString = "none";
try {
Matcher regexMatcher = regex1.matcher(inputStr);
try {
resultString = regexMatcher.replaceAll(replacementStr);
} catch (IllegalArgumentException ex) {
} catch (IndexOutOfBoundsException ex) {
}
} catch (PatternSyntaxException ex) {
}
return resultString;
}
please file a reproduceable test case at http://code.google.com/p/android/issues/entry and i'll have a look. note that i will need a regular expression that reproduces the problem. (our regular expressions are implemented by ICU4C, so the compilation actually happens in native code and this may end up being an ICU bug, but if you file an Android bug i'll worry about upstream.)
If you launched with debugging you can expect it to be about twice as slow as a regular launch. However a minute does seem extraordinary. Some things to suggest, i. look at the console output to see if warnings are being spat out, ii. when it is doing the compile, in the debugger press 'pause' and just see what it is doing. There are ways to get the source, but even so just looking at the call stack may reveal something.