diff options
author | Luis Sigal <luissigal@google.com> | 2011-02-24 17:22:33 +0000 |
---|---|---|
committer | Luis Sigal <luissigal@google.com> | 2011-02-24 19:37:22 +0000 |
commit | 8da3e6ec64b991f5aa1e6561941d130683eba753 (patch) | |
tree | accbe7584ca1f0c14d0d28b2083abd3a5d5b14d0 | |
parent | f80b03ef62a5afecdaf17e8ac9f05aa3ed21307b (diff) | |
download | android_external_android-mock-8da3e6ec64b991f5aa1e6561941d130683eba753.tar.gz android_external_android-mock-8da3e6ec64b991f5aa1e6561941d130683eba753.tar.bz2 android_external_android-mock-8da3e6ec64b991f5aa1e6561941d130683eba753.zip |
Add android-mock to external
Android mock is used by QuickSearchBox to mock classes instead of
creating tons of interfaces.
Change-Id: Ib53ca3a6c5e8e27f42b66cc9e39bbf0d55ed2170
48 files changed, 7800 insertions, 0 deletions
diff --git a/Android.mk b/Android.mk new file mode 100644 index 0000000..6734347 --- /dev/null +++ b/Android.mk @@ -0,0 +1,41 @@ +# Copyright (C) 2010 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_MODULE_TAGS := tests +LOCAL_SDK_VERSION := 8 +LOCAL_STATIC_JAVA_LIBRARIES := easymocklib +LOCAL_MODULE := android-mock-runtimelib +LOCAL_MOCKING_PATH := src/com/google/android/testing/mocking/ +LOCAL_SRC_FILES := \ + $(LOCAL_MOCKING_PATH)/AndroidMock.java \ + $(LOCAL_MOCKING_PATH)/SdkVersion.java \ + $(LOCAL_MOCKING_PATH)/FileUtils.java \ + $(LOCAL_MOCKING_PATH)/GeneratedClassFile.java \ + $(LOCAL_MOCKING_PATH)/MockObject.java \ + $(LOCAL_MOCKING_PATH)/UsesMocks.java + +include $(BUILD_STATIC_JAVA_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE_TAGS := tests +LOCAL_MODULE := android-mock-generatorlib +LOCAL_SRC_FILES := $(call all-java-files-under, src) +LOCAL_STATIC_JAVA_LIBRARIES := easymock javassist + +include $(BUILD_HOST_JAVA_LIBRARY) diff --git a/HOWTO.txt b/HOWTO.txt new file mode 100644 index 0000000..3821d83 --- /dev/null +++ b/HOWTO.txt @@ -0,0 +1 @@ +This is a copy from the android mock project, http://code.google.com/p/android-mock/. To regenerate, use the regenerate_from_source.sh; full instructions about this script are there. diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..75b5248 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,202 @@ +
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/build.properties b/build.properties new file mode 100644 index 0000000..2e10482 --- /dev/null +++ b/build.properties @@ -0,0 +1,8 @@ +easymock-jar=easymock.jar +framework.mock-bin=bin +framework.mock-jar=android_framework_mocks.jar +java-package=com/google/android/testing/mocking +javassist-jar=javassist.jar +lib-folder=lib +source-base=src +staging=staging diff --git a/build.xml b/build.xml new file mode 100644 index 0000000..bad7e25 --- /dev/null +++ b/build.xml @@ -0,0 +1,108 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project name="Android Mock" default="all-tests" basedir="."> + <description> +Android Mock is a wrapper for EasyMock (2.4) which allows for real Class mocking on +an Android (Dalvik) VM. + +All methods on Android Mock are syntactically equivalent to EasyMock method +calls, and will delegate calls to EasyMock, while performing the required +transformations to avoid Dalvik VM troubles. + +Calls directly to EasyMock will work correctly only if the Class being mocked +is in fact an Interface. Calls to Android Mock will work correctly for both +Interfaces and concrete Classes. + +Android Mock requires that the code being mocked be instrumented prior to +loading to the Dalvik VM by having called the MockGenerator.jar file. Try +running java -jar MockGenerator.jar --help for more information. + </description> + + <!-- Global Build Properties --> + <property file="build.properties"/> + + <!-- Imports --> + <import file="src/build-runtime.xml"/> + <import file="src/build-mockgen.xml"/> + <import file="src/build-framework-gen.xml"/> + + <!-- Android Mock Source Jar Properties --> + <property name="source-bin" value="bin/source"/> + <property name="source-lib-jar" value="AndroidMock-src.jar"/> + + <!-- Android Mock Test Properties --> + <property name="junit-jar" value="junit.jar"/> + <property name="test-bin" value="bin/tests"/> + <property name="test-results-folder" value="${test-bin}/results"/> + <property name="test-source-base" value="tests"/> + + <!-- Classpaths --> + <path id="tests.path"> + <pathelement location="${lib-folder}/${easymock-jar}"/> + <pathelement location="${lib-folder}/${javassist-jar}"/> + <pathelement location="${runtime.bin}/${runtime.deploy-jar}"/> + <pathelement location="${mockgen.bin}/${mockgen.deploy-jar}"/> + <pathelement location="${lib-folder}/${junit-jar}"/> + </path> + + <!-- Private Build Targets --> + <target name="-dirs"> + <mkdir dir="${source-bin}"/> + <mkdir dir="${test-bin}"/> + <mkdir dir="${staging}"/> + <mkdir dir="${test-results-folder}"/> + </target> + + <target name="-clean-staging"> + <delete dir="${staging}"/> + </target> + + <!-- Public Build Targets --> + <target name="clean" depends="-clean-staging,mockgen.clean,runtime.clean"> + <delete dir="${source-bin}"/> + <delete dir="${framework-mock-staging}"/> + <delete dir="${test-results-folder}" failonerror="false"/> + <delete dir="${test-bin}" failonerror="false"/> + </target> + + <target name="build-dist" + depends="mockgen.build-deploy, runtime.build-deploy, build-source-lib"/> + + <target name="build-source-lib" depends="-dirs" + description="Builds a jar file containing the Android Mock source files (no dependencies)"> + <jar destfile="${source-bin}/${source-lib-jar}" basedir="${source-base}" includes="**/*.java"/> + </target> + + <!-- Public Test Targets --> + <target name="all-tests" depends="-test-base" + description="Builds the full distribution package and runs all tests storing results in ${test-results-folder}"> + <junit printsummary="no" showoutput="no" reloading="false" failureproperty="testsFailed"> + <formatter type="xml"/> + <classpath location="${test-bin}"/> + <classpath refid="tests.path"/> + <batchtest todir="${test-results-folder}"> + <fileset dir="${test-bin}"> + <include name="**/*Test.class"/> + <include name="**/*Tests.class"/> + </fileset> + </batchtest> + </junit> + <echo>Test Results are available in ${test-results-folder}</echo> + <junitreport todir="${test-results-folder}"> + <fileset dir="${test-results-folder}"> + <include name="TEST-*.xml"/> + </fileset> + <report format="frames" todir="${test-results-folder}/html"/> + </junitreport> + <fail if="testsFailed" message="Tests failed"/> + </target> + + <!-- Private Test Targets --> + <target name="-test-base" depends="build-dist"> + <javac destdir="${test-bin}" target="1.5" srcdir="${test-source-base}" + debug="true" > + <compilerarg value="-proc:none"/> + <classpath refid="tests.path"/> + </javac> + </target> + +</project> diff --git a/livetests/com/google/android/testing/mocking/test/AndroidManifest.xml b/livetests/com/google/android/testing/mocking/test/AndroidManifest.xml new file mode 100644 index 0000000..a35c31e --- /dev/null +++ b/livetests/com/google/android/testing/mocking/test/AndroidManifest.xml @@ -0,0 +1,30 @@ +<!-- +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +--> +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.google.android.testing.mocking.test" + android:versionCode="1" + android:versionName="1.0"> + <application android:label="@string/app_name"> + <uses-library android:name="android.test.runner" /> + </application> + <uses-sdk android:minSdkVersion="3"/> + <instrumentation android:targetPackage="com.google.android.testing.mocking.test" + android:name="android.test.InstrumentationTestRunner" /> +</manifest> + diff --git a/livetests/com/google/android/testing/mocking/test/MockingTest.java b/livetests/com/google/android/testing/mocking/test/MockingTest.java new file mode 100644 index 0000000..ee527a9 --- /dev/null +++ b/livetests/com/google/android/testing/mocking/test/MockingTest.java @@ -0,0 +1,62 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.android.testing.mocking.test; + +import android.content.Context; + +import com.google.android.testing.mocking.AndroidMock; +import com.google.android.testing.mocking.SdkVersion; +import com.google.android.testing.mocking.UsesMocks; +import com.google.android.testing.mocking.testapp.ClassToMock; + +import junit.framework.TestCase; + +/** + * Tests that Android Mock provides correct mocks when running on a Dalvik VM. + * + * @author swoodward (Stephen Woodward) + */ +public class MockingTest extends TestCase { + /** + * Test that an SDK class is mocked correctly, that is to say the mock + * comes from the pre-generated set, and it corresponds to the correct + * runtime environment + */ + @UsesMocks(Context.class) + public void testFrameworkMock() { + Context mockContext = AndroidMock.createMock(Context.class); + String packageName = mockContext.getClass().getPackage().getName(); + assertEquals(SdkVersion.getCurrentVersion().getPackagePrefix(), + packageName.substring(0, packageName.indexOf('.') + 1)); + } + + /** + * Test that a non-SDK class is mocked correctly + */ + @UsesMocks(ClassToMock.class) + public void testNormalMock() { + ClassToMock myMockClass = AndroidMock.createMock(ClassToMock.class); + AndroidMock.expect(myMockClass.getString()).andReturn( + "I'm the king of the world, king of the -- d'oh!"); + AndroidMock.expect(myMockClass.getInt()).andReturn(42); + AndroidMock.replay(myMockClass); + assertEquals("I'm the king of the world, king of the -- d'oh!", + myMockClass.getString()); + assertEquals(42, myMockClass.getInt()); + AndroidMock.verify(myMockClass); + } +} + diff --git a/livetests/com/google/android/testing/mocking/test/res/layout/main.xml b/livetests/com/google/android/testing/mocking/test/res/layout/main.xml new file mode 100644 index 0000000..c1fe411 --- /dev/null +++ b/livetests/com/google/android/testing/mocking/test/res/layout/main.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + > +<TextView + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:text="@string/hello" + /> +</LinearLayout> + diff --git a/livetests/com/google/android/testing/mocking/test/res/values/strings.xml b/livetests/com/google/android/testing/mocking/test/res/values/strings.xml new file mode 100644 index 0000000..224fc4a --- /dev/null +++ b/livetests/com/google/android/testing/mocking/test/res/values/strings.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <string name="hello">Hello World!</string> + <string name="app_name">AndroidMockTestAppTest</string> +</resources> diff --git a/livetests/com/google/android/testing/mocking/testapp/AndroidManifest.xml b/livetests/com/google/android/testing/mocking/testapp/AndroidManifest.xml new file mode 100644 index 0000000..02fdb20 --- /dev/null +++ b/livetests/com/google/android/testing/mocking/testapp/AndroidManifest.xml @@ -0,0 +1,33 @@ +<!-- +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +--> +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.google.android.testing.mocking.testapp" + android:versionCode="1" + android:versionName="1.0"> + <application android:label="@string/app_name"> + <activity android:name=".TestAppActivity" + android:label="@string/app_name"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + </application> + <uses-sdk android:minSdkVersion="3" /> +</manifest> diff --git a/livetests/com/google/android/testing/mocking/testapp/ClassToMock.java b/livetests/com/google/android/testing/mocking/testapp/ClassToMock.java new file mode 100644 index 0000000..8c51220 --- /dev/null +++ b/livetests/com/google/android/testing/mocking/testapp/ClassToMock.java @@ -0,0 +1,31 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.android.testing.mocking.testapp; + +/** + * Dummy class to be mocked. + * + * @author swoodward@google.com (Stephen Woodward) + */ +public class ClassToMock { + public String getString() { + return "foo"; + } + + public int getInt() { + return -1; + } +} diff --git a/livetests/com/google/android/testing/mocking/testapp/TestAppActivity.java b/livetests/com/google/android/testing/mocking/testapp/TestAppActivity.java new file mode 100644 index 0000000..c1d6756 --- /dev/null +++ b/livetests/com/google/android/testing/mocking/testapp/TestAppActivity.java @@ -0,0 +1,36 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.android.testing.mocking.testapp; + +import com.google.android.testing.mocking.testapp.R; +import com.google.android.testing.mocking.testapp.R.layout; + +import android.app.Activity; +import android.os.Bundle; + +/** + * Extremely simple Activity to give our Mocking Test something to test. + * + * @author swoodward@google.com (Stephen Woodward) + */ +public class TestAppActivity extends Activity { + /** Called when the activity is first created. */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.main); + } +} diff --git a/livetests/com/google/android/testing/mocking/testapp/res/layout/main.xml b/livetests/com/google/android/testing/mocking/testapp/res/layout/main.xml new file mode 100644 index 0000000..c1fe411 --- /dev/null +++ b/livetests/com/google/android/testing/mocking/testapp/res/layout/main.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + > +<TextView + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:text="@string/hello" + /> +</LinearLayout> + diff --git a/livetests/com/google/android/testing/mocking/testapp/res/values/strings.xml b/livetests/com/google/android/testing/mocking/testapp/res/values/strings.xml new file mode 100644 index 0000000..e53d336 --- /dev/null +++ b/livetests/com/google/android/testing/mocking/testapp/res/values/strings.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <string name="hello">Hello World, TestAppActivity!</string> + <string name="app_name">AndroidMockTestApp</string> +</resources> diff --git a/regenerate_from_source.sh b/regenerate_from_source.sh new file mode 100644 index 0000000..0dacd87 --- /dev/null +++ b/regenerate_from_source.sh @@ -0,0 +1,17 @@ +#!/bin/bash +# +# Copyright (C) 2011 The Android Open Source Project. +# This script imports the src, test, etc. from android-mock It DOESN'T commit +# to git giving you a chance to review the changes. Remember that changes in +# bin are normally ignored by git, but we need to force them this case. +# This script doesn't take any parameter. + +svn export --force http://android-mock.googlecode.com/svn/trunk/ . +rm lib/easymock.jar +rm lib/junit.jar +rm lib/javassist.jar +rm lib/android/* +# I don't check if there is something on lib on purpose; that way, if +# anything new is added, we will get a visible error. +rmdir lib/android +rmdir lib diff --git a/src/build-framework-gen.xml b/src/build-framework-gen.xml new file mode 100644 index 0000000..c23ba7e --- /dev/null +++ b/src/build-framework-gen.xml @@ -0,0 +1,66 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project name="AndroidMockGenerator" default="frameworkgen.build" basedir=".."> + <!-- Global Properties --> + <property file="../build.properties"/> + + <!-- Imports --> + <import file="build-mockgen.xml"/> + + <!-- Android Framework Mockgen Properties --> + <property name="android-lib-folder" value="${lib-folder}/android"/> + <property name="framework.mock-staging" value="staging/android_framework_mocks"/> + <property name="android-15-jar" value="android_v15.jar"/> + <property name="android-16-jar" value="android_v16.jar"/> + <property name="android-201-jar" value="android_v201.jar"/> + <property name="android-21-jar" value="android_v21.jar"/> + <property name="android-22-jar" value="android_v22.jar"/> + + <!-- property name="framework.mock-bin" value=see build.properties --> + <!-- property name="framework.mock-jar" value=see build.properties --> + + <!-- Private Targets --> + <target name="-frameworkgen.dirs"> + <mkdir dir="${framework.mock-bin}"/> + <mkdir dir="${framework.mock-staging}"/> + </target> + + <target name="-frameworkgen.clean-staging"> + <delete dir="${staging}"/> + </target> + + <!-- Public Targets --> + <target name="frameworkgen.clean" depends="-frameworkgen.clean-staging"> + <delete dir="${framework.mock-bin}/${framework.mock-jar}"/> + <delete dir="${framework.mock-staging}"/> + </target> + + <target name="frameworkgen.build" + depends="-frameworkgen.clean-staging,-frameworkgen.dirs,mockgen.build-deploy" + description="Builds the mock support files for mocking Android Framework classes."> + + <generate-framework-mocks api-jar-file="${android-15-jar}" api-level="3"/> + <generate-framework-mocks api-jar-file="${android-16-jar}" api-level="4"/> + <generate-framework-mocks api-jar-file="${android-201-jar}" api-level="6"/> + <generate-framework-mocks api-jar-file="${android-21-jar}" api-level="7"/> + <generate-framework-mocks api-jar-file="${android-22-jar}" api-level="8"/> + <jar destfile="${framework.mock-bin}/${framework.mock-jar}" + basedir="${framework.mock-staging}" includes="**/*.class" index="true"/> + </target> + + <!-- Macros --> + <macrodef name="generate-framework-mocks"> + <attribute name="api-jar-file"/> + <attribute name="api-level"/> + <sequential> + <java classname="com.google.android.testing.mocking.AndroidFrameworkMockGenerator" fork="true"> + <classpath> + <pathelement location="${mockgen.bin}/${mockgen.deploy-jar}"/> + <pathelement location="${android-lib-folder}/@{api-jar-file}"/> + </classpath> + <arg value="${framework.mock-staging}"/> + <arg value="@{api-level}"/> + </java> + </sequential> + </macrodef> + +</project>
\ No newline at end of file diff --git a/src/build-mockgen.xml b/src/build-mockgen.xml new file mode 100644 index 0000000..3a9a9c4 --- /dev/null +++ b/src/build-mockgen.xml @@ -0,0 +1,82 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project name="AndroidMockGenerator" default="mockgen.build" basedir=".."> + <!-- Global Properties --> + <property file="../build.properties"/> + + <!-- Android Mock Generator Properties --> + <property name="mockgen.bin" value="bin"/> + <property name="mockgen.class-bin" value="bin/classes"/> + <property name="mockgen.deploy-jar" value="AndroidMockGenerator.jar"/> + <property name="mockgen.includes" value="${java-package}/GeneratedMockJar.readme"/> + <property name="mockgen.java-files" value="com/google/android/testing/mocking/*.java"/> + <property name="mockgen.nodeps-jar" value="AndroidMockGenerator-nodeps.jar"/> + <property name="meta-service-folder" value="META-INF/services"/> + <property name="annotations-meta-file" + value="${meta-service-folder}/javax.annotation.processing.Processor"/> + + <!-- Classpaths --> + <path id="generator.path"> + <pathelement location="${lib-folder}/${easymock-jar}"/> + <pathelement location="${lib-folder}/${javassist-jar}"/> + </path> + + <!-- Private Targets --> + <target name="-mockgen.dirs"> + <mkdir dir="${mockgen.bin}"/> + <mkdir dir="${mockgen.class-bin}"/> + <mkdir dir="${mockgen.class-bin}/${meta-service-folder}"/> + </target> + + <target name="-mockgen.clean-staging"> + <delete dir="${staging}"/> + </target> + + <!-- Public Targets --> + <target name="mockgen.clean" depends="-mockgen.clean-staging"> + <delete file="${mockgen.deploy-jar}"/> + <delete file="${mockgen.nodeps-jar}"/> + <delete dir="${mockgen.class-bin}"/> + </target> + + <target name="mockgen.build" depends="-mockgen.clean-staging,-mockgen.dirs" + description="Builds the Mock Generator jar file"> + <javac destdir="${mockgen.class-bin}" srcdir="${source-base}" + includes="${mockgen.java-files}" debug="true"> + <classpath refid="generator.path"/> + </javac> + <copy todir="${mockgen.class-bin}"> + <fileset dir="${source-base}" includes="${mockgen.includes}"/> + </copy> + <echo file="${mockgen.class-bin}/${annotations-meta-file}" + message="com.google.android.testing.mocking.UsesMocksProcessor"/> + <manifestclasspath property="frameworkjar.classpath" + jarfile="${mockgen.bin}/${mockgen.nodeps-jar}"> + <classpath location="${framework.mock-bin}/${framework.mock-jar}"/> + </manifestclasspath> + <tstamp> + <format property="build.time" pattern="dd-MMMM-yyyy hh:mm aa"/> + </tstamp> + <jar destfile="${mockgen.bin}/${mockgen.nodeps-jar}" basedir="${mockgen.class-bin}"> + <manifest> + <attribute name="Built-On" value="${build.time}"/> + </manifest> + </jar> + </target> + + <target name="mockgen.build-deploy" depends="mockgen.build" + description="Builds the Mock Generator jar file with dependencies included."> + <unjar dest="${staging}"> + <fileset dir="."> + <include name="${mockgen.bin}/${mockgen.nodeps-jar}"/> + <include name="${lib-folder}/${easymock-jar}"/> + <include name="${lib-folder}/${javassist-jar}"/> + </fileset> + </unjar> + <tstamp/> + <jar destfile="${mockgen.bin}/${mockgen.deploy-jar}" basedir="${staging}"> + <manifest> + <attribute name="Built-On" value="${build.time}"/> + </manifest> + </jar> + </target> +</project>
\ No newline at end of file diff --git a/src/build-runtime.xml b/src/build-runtime.xml new file mode 100644 index 0000000..5516985 --- /dev/null +++ b/src/build-runtime.xml @@ -0,0 +1,55 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project name="AndroidMockRuntime" default="runtime.build" basedir=".."> + <!-- Global Properties --> + <property file="../build.properties"/> + + <!-- Android Mock Runtime Properties --> + <property name="runtime.bin" value="bin"/> + <property name="runtime.class-bin" value="bin/classes"/> + <property name="runtime.deploy-jar" value="AndroidMockRuntime.jar"/> + <property name="runtime.java-files" value="${java-package}/AndroidMock.java + ${java-package}/MockObject.java ${java-package}/UsesMocks.java"/> + <property name="runtime.nodeps-jar" value="AndroidMockRuntime-nodeps.jar"/> + + <!-- Classpaths --> + <path id="path"> + <pathelement location="${lib-folder}/${easymock-jar}"/> + </path> + + <!-- Private Build Targets --> + <target name="-runtime.dirs"> + <mkdir dir="${runtime.bin}"/> + <mkdir dir="${runtime.class-bin}"/> + </target> + + <target name="-runtime.clean-staging"> + <delete dir="${staging}"/> + </target> + + <!-- Public Build Targets --> + <target name="runtime.clean" depends="-runtime.clean-staging"> + <delete file="${runtime.deploy-jar}"/> + <delete file="${runtime.nodeps-jar}"/> + <delete dir="${runtime.class-bin}"/> + </target> + + <target name="runtime.build" depends="-runtime.clean-staging,-runtime.dirs" + description="Builds the Android Mock Runtime library jar file without dependencies included."> + <javac destdir="${runtime.class-bin}" target="1.5" srcdir="${source-base}" + includes="${runtime.java-files}" debug="true"> + <classpath refid="path"/> + </javac> + <jar destfile="${runtime.bin}/${runtime.nodeps-jar}" basedir="${runtime.class-bin}"/> + </target> + + <target name="runtime.build-deploy" depends="runtime.build" + description="Builds the Android Mock Runtime library jar file with dependencies included."> + <unjar dest="${staging}"> + <fileset dir="."> + <include name="${runtime.bin}/${runtime.nodeps-jar}"/> + <include name="${lib-folder}/${easymock-jar}"/> + </fileset> + </unjar> + <jar destfile="${runtime.bin}/${runtime.deploy-jar}" basedir="${staging}"/> + </target> +</project>
\ No newline at end of file diff --git a/src/com/google/android/testing/mocking/AndroidFrameworkMockGenerator.java b/src/com/google/android/testing/mocking/AndroidFrameworkMockGenerator.java new file mode 100644 index 0000000..d634c24 --- /dev/null +++ b/src/com/google/android/testing/mocking/AndroidFrameworkMockGenerator.java @@ -0,0 +1,170 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.android.testing.mocking; + +import javassist.CannotCompileException; +import javassist.CtClass; +import javassist.NotFoundException; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +/** + * Mock Generator for Android Framework classes. + * + * <p> + * This class will pull pre-defined mocks for Android framework classes. This is + * needed because a project might build against version X and then run against + * version Y, where the specification of the class being mocked will have + * changed, rendering the mock invalid. + * + * @author swoodward@google.com (Stephen Woodward) + */ +public class AndroidFrameworkMockGenerator { + private static final String LIB_FOLDER = "lib"; + private static final String JAR_NAME = "android_"; + + /** + * Returns a set of mock support classes for the specified Class for all versions of + * the Android SDK. If the requested class is not part of the Android framework, then the class + * will not be found and an exception will be thrown. + * + * @param clazz the class to mock. + * @return All available mock support classes (for all known Android SDKs) for + * the requested Class. + */ + public List<GeneratedClassFile> getMocksForClass(Class<?> clazz) throws ClassNotFoundException, + IOException { + List<Class<?>> prebuiltClasses = getPrebuiltClassesFor(clazz); + List<GeneratedClassFile> classList = new ArrayList<GeneratedClassFile>(); + for (Class<?> prebuiltClass : prebuiltClasses) { + try { + CtClass ctClass = AndroidMockGenerator.getClassPool().get(prebuiltClass.getName()); + classList.add(new GeneratedClassFile(ctClass.getName(), ctClass.toBytecode())); + } catch (NotFoundException e) { + throw new ClassNotFoundException("Missing class while fetching prebuilt mocks: " + + prebuiltClass.getName(), e); + } catch (CannotCompileException e) { + throw new RuntimeException("Internal Error copying a prebuilt mock: " + + prebuiltClass.getName(), e); + } + } + return classList; + } + + private List<Class<?>> getPrebuiltClassesFor(Class<?> clazz) throws ClassNotFoundException { + List<Class<?>> classes = new ArrayList<Class<?>>(); + SdkVersion[] versions = SdkVersion.getAllVersions(); + for (SdkVersion sdkVersion : versions) { + classes.add(Class.forName(FileUtils.getSubclassNameFor(clazz, sdkVersion))); + classes.add(Class.forName(FileUtils.getInterfaceNameFor(clazz, sdkVersion))); + } + return classes; + } + + /** + * @return a List of {@link GeneratedClassFile} objects representing the mocks for the specified + * class for a single version of the Android SDK. + */ + public List<GeneratedClassFile> createMocksForClass(Class<?> clazz, SdkVersion version) + throws ClassNotFoundException, IOException, CannotCompileException { + AndroidMockGenerator mockGenerator = new AndroidMockGenerator(); + List<GeneratedClassFile> mocks = new ArrayList<GeneratedClassFile>(); + mocks.addAll(mockGenerator.createMocksForClass(clazz, version)); + return mocks; + } + + /** + * @return A list of all class files inside the provided jar file. + */ + List<Class<?>> getClassList(JarFile jar) throws ClassNotFoundException { + return getClassList(Collections.list(jar.entries())); + } + + List<Class<?>> getClassList(Collection<JarEntry> jarEntries) throws ClassNotFoundException { + List<Class<?>> classList = new ArrayList<Class<?>>(); + for (JarEntry jarEntry : jarEntries) { + if (jarEntryIsClassFile(jarEntry)) { + classList.add(Class.forName(FileUtils.getClassNameFor(jarEntry.getName()))); + } + } + return classList; + } + + /** + * @return true if the provided JarEntry represents a class file. + */ + boolean jarEntryIsClassFile(JarEntry jarEntry) { + return jarEntry.getName().endsWith(".class"); + } + + /** + * @return the Android framework jar file for the specified {@link SdkVersion}. + */ + static String getJarFileNameForVersion(SdkVersion version) { + return new StringBuilder() + .append(LIB_FOLDER) + .append(File.separator) + .append("android") + .append(File.separator) + .append(JAR_NAME) + .append(version.getVersionName()) + .append(".jar").toString(); + } + + private static Set<GeneratedClassFile> generateMocks( + SdkVersion version, JarFile jar) + throws ClassNotFoundException, IOException, CannotCompileException { + AndroidFrameworkMockGenerator mockGenerator = new AndroidFrameworkMockGenerator(); + List<Class<?>> classList = mockGenerator.getClassList(jar); + Set<GeneratedClassFile> classes = new HashSet<GeneratedClassFile>(); + for (Class<?> clazz : classList) { + classes.addAll(mockGenerator.createMocksForClass(clazz, version)); + } + return classes; + } + + private static JarFile getJarFile(SdkVersion version) throws IOException { + File jarFile = new File(getJarFileNameForVersion(version)).getAbsoluteFile(); + System.out.println("Using Jar File: " + jarFile.getAbsolutePath()); + return new JarFile(jarFile); + } + + public static void main(String[] args) { + try { + String outputFolderName = args[0]; + SdkVersion version = SdkVersion.getVersionFor(Integer.parseInt(args[1])); + System.out.println("Generating files for " + version.getPackagePrefix()); + + JarFile jar = getJarFile(version); + + Set<GeneratedClassFile> classes = generateMocks(version, jar); + for (GeneratedClassFile clazz : classes) { + FileUtils.saveClassToFolder(clazz, outputFolderName); + } + } catch (Exception e) { + throw new RuntimeException("Internal error generating framework mocks", e); + } + } +} diff --git a/src/com/google/android/testing/mocking/AndroidMock.java b/src/com/google/android/testing/mocking/AndroidMock.java new file mode 100644 index 0000000..88f79b1 --- /dev/null +++ b/src/com/google/android/testing/mocking/AndroidMock.java @@ -0,0 +1,2804 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.testing.mocking; + +import org.easymock.Capture; +import org.easymock.EasyMock; +import org.easymock.IArgumentMatcher; +import org.easymock.IExpectationSetters; +import org.easymock.LogicalOperator; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +/** + * Android Mock is a wrapper for EasyMock (2.4) which allows for real Class mocking on + * an Android (Dalvik) VM. + * + * All methods on Android Mock are syntactically equivalent to EasyMock method + * calls, and will delegate calls to EasyMock, while performing the required + * transformations to avoid Dalvik VM troubles. + * + * Calls directly to EasyMock will work correctly only if the Class being mocked + * is in fact an Interface. Calls to Android Mock will work correctly for both + * Interfaces and concrete Classes. + * + * Android Mock requires that the code being mocked be instrumented prior to + * loading to the Dalvik VM by having called the MockGenerator.jar file. Try + * running {@code java -jar MockGenerator.jar --help} for more information. + * + * An example usage pattern is: + * + * {@code @UsesMocks(MyClass.class) public void testFoo() { MyClass + * mockObject = AndroidMock.createMock(MyClass.class); + * AndroidMock.expect(mockObject.foo(0)).andReturn(42); + * AndroidMock.replay(mockObject); assertEquals(42, mockObject.foo(0)); + * AndroidMock.verify(mockObject); } * } + * + * + * <b>A note about parameter and return types for the <i>expects</i> style of methods.</b> + * The various expectation methods such as {@link #eq(boolean)}, {@link #and(boolean, boolean)}, + * and {@link #leq(byte)} all have nonsense return values. Each of the expectation methods may only + * be executed under strict conditions (in order to set expectations of incoming method parameters + * during record mode) and thus their return types are in fact never used. The return types are set + * only to satisfy the compile-time parameter requirements of the methods being mocked in order to + * allow code such as: {@code mockObject.doFoo(anyInt());}. If {@link #anyInt()} did not return + * {@code int} then the compiler would not accept the preceding code fragment. + * + * Similarly, the complex expectation methods ({@code #and}, {@code #or}, and {@code not}) take + * various parameter types, but will throw an {@link java.lang.IllegalStateException} if anything + * other than an expectation method is provided. E.g. {@code mockObject.doFoo(and(gt(5), lt(10));} + * + * The benefit of this is to make it very easy to read the test code after it has been written. + * Additionally, the test code is protected by type safety at compile time. + * + * The downside of this is that when writing the test code in the record phase, how to use the + * expectation APIs is not made clear by the method signatures of these expectation methods. In + * particular, it's not at all clear that {@link #and(byte, byte)} takes as parameters other + * expectation methods, and not just any random method that returns a {@literal byte} or even a + * {@literal byte} literal. + * + * @author swoodward@google.com (Stephen Woodward) + */ +public class AndroidMock { + private AndroidMock() { + } + + /** + * Creates a mock object for the specified class, order checking + * is enabled by default. The difference between a strict mock and a normal mock is that a strict + * mock will not allow for invocations of the mock object to occur other than in the exact order + * specified during record mode. + * + * The parameter {@literal args} allows the caller to choose which constructor on the Class + * specified by {@literal toMock} to be called when constructing the Mock object. If a constructor + * takes primitive values, Java Auto-boxing/unboxing will take care of it automatically, allowing + * the caller to make calls such as {@literal createStrictMock(MyObject.class, 42, "hello!")}, + * where {@literal MyObject} defines a constructor such as + * {@literal public MyObject(int answer, String greeting)}. + * + * @param <T> the class type to be mocked. + * @param toMock the class of the object to be mocked. + * @param args the arguments to pass to the constructor. + * @return the mock object. + */ + public static <T> T createStrictMock(Class<T> toMock, Object... args) { + return createStrictMock(null, toMock, args); + } + + /** + * Creates a mock object for the specified class, order checking + * is enabled by default. The difference between a strict mock and a normal mock is that a strict + * mock will not allow for invocations of the mock object to occur other than in the exact order + * specified during record mode. + * + * The parameter {@literal args} allows the caller to choose which constructor on the Class + * specified by {@literal toMock} to be called when constructing the Mock object. If a constructor + * takes primitive values, Java Auto-boxing/unboxing will take care of it automatically, allowing + * the caller to make calls such as + * {@literal createStrictMock("NameMyMock", MyObject.class, 42, "hello!")}, + * where {@literal MyObject} defines a constructor such as + * {@literal public MyObject(int answer, String greeting)}. + * + * @param <T> the class type to be mocked. + * @param name the name of the mock object. This must be a valid Java identifier. This value is + * used as the return value from {@link #toString()} when invoked on the mock object. + * @param toMock the class of the object to be mocked. + * @param args the arguments to pass to the constructor. + * @return the mock object. + * @throws IllegalArgumentException if the name is not a valid Java identifier. + */ + @SuppressWarnings("cast") + public static <T> T createStrictMock(String name, Class<T> toMock, Object... args) { + if (toMock.isInterface()) { + return EasyMock.createStrictMock(name, toMock); + } + Object mockedInterface = EasyMock.createStrictMock(name, getInterfaceFor(toMock)); + return (T) getSubclassFor(toMock, getInterfaceFor(toMock), mockedInterface, args); + } + + /** + * Creates a mock object for the specified class, order checking + * is disabled by default. A normal mock with order checking disabled will allow you to record + * the method invocations during record mode in any order. If order is important, use + * {@link #createStrictMock(Class, Object...)} instead. + * + * The parameter {@literal args} allows the caller to choose which constructor on the Class + * specified by {@literal toMock} to be called when constructing the Mock object. If a constructor + * takes primitive values, Java Auto-boxing/unboxing will take care of it automatically, allowing + * the caller to make calls such as + * {@literal createMock(MyObject.class, 42, "hello!")}, + * where {@literal MyObject} defines a constructor such as + * {@literal public MyObject(int answer, String greeting)}. + * + * @param <T> the type of the class to be mocked. + * @param toMock the class object representing the class to be mocked. + * @param args the arguments to pass to the constructor. + * @return the mock object. + */ + public static <T> T createMock(Class<T> toMock, Object... args) { + return createMock(null, toMock, args); + } + + /** + * Creates a mock object for the specified class, order checking + * is disabled by default. A normal mock with order checking disabled will allow you to record + * the method invocations during record mode in any order. If order is important, use + * {@link #createStrictMock(Class, Object...)} instead. + * + * The parameter {@literal args} allows the caller to choose which constructor on the Class + * specified by {@literal toMock} to be called when constructing the Mock object. If a constructor + * takes primitive values, Java Auto-boxing/unboxing will take care of it automatically, allowing + * the caller to make calls such as + * {@literal createMock("NameMyMock", MyObject.class, 42, "hello!")}, + * where {@literal MyObject} defines a constructor such as + * {@literal public MyObject(int answer, String greeting)}. + * + * @param <T> the type of the class to be mocked. + * @param name the name of the mock object. This must be a valid Java identifier. This value is + * used as the return value from {@link #toString()} when invoked on the mock object. + * @param toMock the class object representing the class to be mocked. + * @param args the arguments to pass to the constructor. + * @return the mock object. + * @throws IllegalArgumentException if the name is not a valid Java identifier. + */ + @SuppressWarnings("cast") + public static <T> T createMock(String name, Class<T> toMock, Object... args) { + if (toMock.isInterface()) { + return EasyMock.createMock(name, toMock); + } + Object mockedInterface = EasyMock.createMock(name, getInterfaceFor(toMock)); + return (T) getSubclassFor(toMock, getInterfaceFor(toMock), mockedInterface, args); + } + + /** + * Creates a mock object for the specified class, order checking + * is disabled by default, and the mock object will return {@code 0}, + * {@code null} or {@code false} for unexpected invocations. + * + * The parameter {@literal args} allows the caller to choose which constructor on the Class + * specified by {@literal toMock} to be called when constructing the Mock object. If a constructor + * takes primitive values, Java Auto-boxing/unboxing will take care of it automatically, allowing + * the caller to make calls such as + * {@literal createNiceMock(MyObject.class, 42, "hello!")}, + * where {@literal MyObject} defines a constructor such as + * {@literal public MyObject(int answer, String greeting)}. + * + * @param <T> the type of the class to be mocked. + * @param toMock the class object representing the class to be mocked. + * @param args the arguments to pass to the constructor. + * @return the mock object. + */ + public static <T> T createNiceMock(Class<T> toMock, Object... args) { + return createNiceMock(null, toMock, args); + } + + /** + * Creates a mock object for the specified class, order checking + * is disabled by default, and the mock object will return {@code 0}, + * {@code null} or {@code false} for unexpected invocations. + * + * The parameter {@literal args} allows the caller to choose which constructor on the Class + * specified by {@literal toMock} to be called when constructing the Mock object. If a constructor + * takes primitive values, Java Auto-boxing/unboxing will take care of it automatically, allowing + * the caller to make calls such as + * {@literal createNiceMock("NameMyMock", MyObject.class, 42, "hello!")}, + * where {@literal MyObject} defines a constructor such as + * {@literal public MyObject(int answer, String greeting)}. + * + * @param <T> the type of the class to be mocked. + * @param name the name of the mock object. This must be a valid Java identifier. This value is + * used as the return value from {@link #toString()} when invoked on the mock object. + * @param toMock the class object representing the class to be mocked. + * @param args the arguments to pass to the constructor. + * @throws IllegalArgumentException if the name is not a valid Java identifier. + */ + @SuppressWarnings("cast") + public static <T> T createNiceMock(String name, Class<T> toMock, Object... args) { + if (toMock.isInterface()) { + return EasyMock.createNiceMock(name, toMock); + } + Object mockedInterface = EasyMock.createNiceMock(name, getInterfaceFor(toMock)); + return (T) getSubclassFor(toMock, getInterfaceFor(toMock), mockedInterface, args); + } + + /** + * Returns the expectation setter for the last expected invocation in the current thread. + * Expectation setters are used during the recording phase to specify what method calls + * will be expected during the replay phase, and with which parameters. Parameters may be + * specified as literal values (e.g. {@code expect(mock.foo(42)); expect(mock.foo("hello"));}) + * or according to parameter expectation criteria. Some examples of parameter expectation + * criteria include {@link #anyObject()}, {@link #leq(int)}, {@link #contains(String)}, + * {@link #isA(Class)} and also the more complex {@link #and(char, char)}, + * {@link #or(boolean, boolean)}, and {@link #not(double)}. + * + * An {@link org.easymock.IExpectationSetters} object has methods which allow you to define + * the expected behaviour of the mocked method and the expected number of invocations, + * e.g. {@link org.easymock.IExpectationSetters#andReturn(Object)}, + * {@link org.easymock.IExpectationSetters#andThrow(Throwable)}, and + * {@link org.easymock.IExpectationSetters#atLeastOnce()}. + * + * @param expectedValue the parameter is used to transport the type to the ExpectationSetter. + * It allows writing the expected call as an argument, + * e.g. {@code expect(mock.getName()).andReturn("John Doe")}. + * @return the expectation setter. + */ + public static <T> IExpectationSetters<T> expect(T expectedValue) { + return EasyMock.expect(expectedValue); + } + + /** + * Returns the expectation setter for the last expected invocation in the + * current thread. This method is used for expected invocations on void + * methods. Use this for things such as + * {@link org.easymock.IExpectationSetters#andThrow(Throwable)} + * on void methods. + * E.g. + * {@code mock.doFoo(); + * AndroidMock.expectLastCall().andThrow(new IllegalStateException());} + * + * @see #expect(Object) for more details about {@link org.easymock.IExpectationSetters} + * @return the expectation setter. + */ + public static <T> IExpectationSetters<T> expectLastCall() { + return EasyMock.expectLastCall(); + } + + /** + * Expects any {@code boolean} argument as a parameter to a mocked method. + * Use this to loosen the expectations of acceptable parameters for a mocked method call. + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.anyBoolean())).andReturn("hello world");} + * + * @return {@code false}. The return value is always ignored. + */ + public static boolean anyBoolean() { + return EasyMock.anyBoolean(); + } + + /** + * Expects any {@code byte} argument as a parameter to a mocked method. + * Use this to loosen the expectations of acceptable parameters for a mocked method call. + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.anyByte())).andReturn("hello world");} + * + * @return {@code 0}. The return value is always ignored. + */ + public static byte anyByte() { + return EasyMock.anyByte(); + } + + /** + * Expects any {@code char} argument as a parameter to a mocked method. + * Use this to loosen the expectations of acceptable parameters for a mocked method call. + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.anyChar())).andReturn("hello world");} + * + * @return {@code 0}. The return value is always ignored. + */ + public static char anyChar() { + return EasyMock.anyChar(); + } + + /** + * Expects any {@code int} argument as a parameter to a mocked method. + * Use this to loosen the expectations of acceptable parameters for a mocked method call. + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.anyInt())).andReturn("hello world");} + * + * @return {@code 0}. The return value is always ignored. + */ + public static int anyInt() { + return EasyMock.anyInt(); + } + + /** + * Expects any {@code long} argument as a parameter to a mocked method. + * Use this to loosen the expectations of acceptable parameters for a mocked method call. + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.anyLong())).andReturn("hello world");} + * + * @return {@code 0}. The return value is always ignored. + */ + public static long anyLong() { + return EasyMock.anyLong(); + } + + /** + * Expects any {@code float} argument as a parameter to a mocked method. + * Use this to loosen the expectations of acceptable parameters for a mocked method call. + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.anyFloat())).andReturn("hello world");} + * + * @return {@code 0}. The return value is always ignored. + */ + public static float anyFloat() { + return EasyMock.anyFloat(); + } + + /** + * Expects any {@code double} argument as a parameter to a mocked method. + * Use this to loosen the expectations of acceptable parameters for a mocked method call. + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.anyDouble())).andReturn("hello world");} + * + * @return {@code 0}. The return value is always ignored. */ + public static double anyDouble() { + return EasyMock.anyDouble(); + } + + /** + * Expects any {@code short} argument as a parameter to a mocked method. + * Use this to loosen the expectations of acceptable parameters for a mocked method call. + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.anyShort())).andReturn("hello world");} + * + * @return {@code 0}. The return value is always ignored. */ + public static short anyShort() { + return EasyMock.anyShort(); + } + + /** + * Expects any {@code java.lang.Object} (or subclass) argument as a parameter to a mocked method. + * Note that this includes Arrays (since an array {@literal is an Object}) + * Use this to loosen the expectations of acceptable parameters for a mocked method call. + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.anyLong())).andReturn("hello world");} + * + * @return {@code 0}. The return value is always ignored. + */ + @SuppressWarnings("unchecked") + public static <T> T anyObject() { + return (T) EasyMock.anyObject(); + } + + /** + * Expects a {@code Comparable} argument greater than or equal to the given value as a parameter + * to a mocked method. + * + * Use this to loosen the expectations of acceptable parameters for a mocked method call. + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.geq("hi"))).andReturn("hello");} + * + * @param expectedValue the value to which the specified incoming parameter to the mocked method + * must be greater than or equal. + * @return {@code null}. The return value is always ignored. + */ + public static <T extends Comparable<T>> T geq(Comparable<T> expectedValue) { + return EasyMock.geq(expectedValue); + } + + /** + * Expects a {@code byte} argument greater than or equal to the given value as a parameter + * to a mocked method. + * + * Use this to loosen the expectations of acceptable parameters for a mocked method call. + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.geq((byte)42))).andReturn("hello");} + * + * @param expectedValue the value to which the specified incoming parameter to the mocked method + * must be greater than or equal. + * @return {@code 0}. The return value is always ignored. + */ + public static byte geq(byte expectedValue) { + return EasyMock.geq(expectedValue); + } + + /** + * Expects a {@code double} argument greater than or equal to the given value as a parameter + * to a mocked method. + * + * Use this to loosen the expectations of acceptable parameters for a mocked method call. + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.geq(42.0))).andReturn("hello");} + * + * @param expectedValue the value to which the specified incoming parameter to the mocked method + * must be greater than or equal. + * @return {@code 0}. The return value is always ignored. + */ + public static double geq(double expectedValue) { + return EasyMock.geq(expectedValue); + } + + /** + * Expects a {@code float} argument greater than or equal to the given value as a parameter + * to a mocked method. + * + * Use this to loosen the expectations of acceptable parameters for a mocked method call. + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.geq(42.0f))).andReturn("hello");} + * + * @param expectedValue the value to which the specified incoming parameter to the mocked method + * must be greater than or equal. + * @return {@code 0}. The return value is always ignored. + */ + public static float geq(float expectedValue) { + return EasyMock.geq(expectedValue); + } + + /** + * Expects an {@code int} argument greater than or equal to the given value as a parameter + * to a mocked method. + * + * Use this to loosen the expectations of acceptable parameters for a mocked method call. + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.geq(42))).andReturn("hello");} + * + * @param expectedValue the value to which the specified incoming parameter to the mocked method + * must be greater than or equal. + * @return {@code 0}. The return value is always ignored. + */ + public static int geq(int expectedValue) { + return EasyMock.geq(expectedValue); + } + + /** + * Expects a {@code long} argument greater than or equal to the given value as a parameter + * to a mocked method. + * + * Use this to loosen the expectations of acceptable parameters for a mocked method call. + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.geq(42l))).andReturn("hello");} + * + * @param expectedValue the value to which the specified incoming parameter to the mocked method + * must be greater than or equal. + * @return {@code 0}. The return value is always ignored. + */ + public static long geq(long expectedValue) { + return EasyMock.geq(expectedValue); + } + + /** + * Expects a {@code short} argument greater than or equal to the given value as a parameter + * to a mocked method. + * + * Use this to loosen the expectations of acceptable parameters for a mocked method call. + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.geq((short)42))).andReturn("hello");} + * + * @param expectedValue the value to which the specified incoming parameter to the mocked method + * must be greater than or equal. + * @return {@code 0}. The return value is always ignored. + */ + public static short geq(short expectedValue) { + return EasyMock.geq(expectedValue); + } + + /** + * Expects a {@code Comparable} argument less than or equal to the given value as a parameter + * to a mocked method. + * + * Use this to loosen the expectations of acceptable parameters for a mocked method call. + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.leq("hi"))).andReturn("hello");} + * + * @param expectedValue the value to which the specified incoming parameter to the mocked method + * must be less than or equal. + * @return {@code null}. The return value is always ignored. + */ + public static <T extends Comparable<T>> T leq(Comparable<T> expectedValue) { + return EasyMock.leq(expectedValue); + } + + /** + * Expects a {@code byte} argument less than or equal to the given value as a parameter + * to a mocked method. + * + * Use this to loosen the expectations of acceptable parameters for a mocked method call. + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.leq((byte)42))).andReturn("hello");} + * + * @param expectedValue the value to which the specified incoming parameter to the mocked method + * must be less than or equal. + * @return {@code 0}. The return value is always ignored. + */ + public static byte leq(byte expectedValue) { + return EasyMock.leq(expectedValue); + } + + /** + * Expects a {@code double} argument less than or equal to the given value as a parameter + * to a mocked method. + * + * Use this to loosen the expectations of acceptable parameters for a mocked method call. + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.leq(42.0))).andReturn("hello");} + * + * @param expectedValue the value to which the specified incoming parameter to the mocked method + * must be less than or equal. + * @return {@code 0}. The return value is always ignored. + */ + public static double leq(double expectedValue) { + return EasyMock.leq(expectedValue); + } + + /** + * Expects a {@code float} argument less than or equal to the given value as a parameter + * to a mocked method. + * + * Use this to loosen the expectations of acceptable parameters for a mocked method call. + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.leq(42.0f))).andReturn("hello");} + * + * @param expectedValue the value to which the specified incoming parameter to the mocked method + * must be less than or equal. + * @return {@code 0}. The return value is always ignored. + */ + public static float leq(float expectedValue) { + return EasyMock.leq(expectedValue); + } + + /** + * Expects an {@code int} argument less than or equal to the given value as a parameter + * to a mocked method. + * + * Use this to loosen the expectations of acceptable parameters for a mocked method call. + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.leq(42))).andReturn("hello");} + * + * @param expectedValue the value to which the specified incoming parameter to the mocked method + * must be less than or equal. + * @return {@code 0}. The return value is always ignored. + */ + public static int leq(int expectedValue) { + return EasyMock.leq(expectedValue); + } + + /** + * Expects a {@code long} argument less than or equal to the given value as a parameter + * to a mocked method. + * + * Use this to loosen the expectations of acceptable parameters for a mocked method call. + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.leq(42l))).andReturn("hello");} + * + * @param expectedValue the value to which the specified incoming parameter to the mocked method + * must be less than or equal. + * @return {@code 0}. The return value is always ignored. + */ + public static long leq(long expectedValue) { + return EasyMock.leq(expectedValue); + } + + /** + * Expects a {@code short} argument less than or equal to the given value as a parameter + * to a mocked method. + * + * Use this to loosen the expectations of acceptable parameters for a mocked method call. + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.leq((short)42))).andReturn("hello");} + * + * @param expectedValue the value to which the specified incoming parameter to the mocked method + * must be less than or equal. + * @return {@code 0}. The return value is always ignored. + */ + public static short leq(short expectedValue) { + return EasyMock.leq(expectedValue); + } + + /** + * Expects a {@code Comparable} argument greater than the given value as a parameter + * to a mocked method. + * + * Use this to loosen the expectations of acceptable parameters for a mocked method call. + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.gt("hi"))).andReturn("hello");} + * + * @param expectedValue the value to which the specified incoming parameter to the mocked method + * must be greater than. + * @return {@code null}. The return value is always ignored. + */ + public static <T extends Comparable<T>> T gt(Comparable<T> expectedValue) { + return EasyMock.gt(expectedValue); + } + + /** + * Expects a {@code byte} argument greater than the given value as a parameter + * to a mocked method. + * + * Use this to loosen the expectations of acceptable parameters for a mocked method call. + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.gt((byte)42))).andReturn("hello");} + * + * @param expectedValue the value to which the specified incoming parameter to the mocked method + * must be greater than. + * @return {@code 0}. The return value is always ignored. + */ + public static byte gt(byte expectedValue) { + return EasyMock.gt(expectedValue); + } + + /** + * Expects a {@code double} argument greater than the given value as a parameter + * to a mocked method. + * + * Use this to loosen the expectations of acceptable parameters for a mocked method call. + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.gt(42.0))).andReturn("hello");} + * + * @param expectedValue the value to which the specified incoming parameter to the mocked method + * must be greater than. + * @return {@code 0}. The return value is always ignored. + */ + public static double gt(double expectedValue) { + return EasyMock.gt(expectedValue); + } + + /** + * Expects a {@code float} argument greater than the given value as a parameter + * to a mocked method. + * + * Use this to loosen the expectations of acceptable parameters for a mocked method call. + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.gt(42.0f))).andReturn("hello");} + * + * @param expectedValue the value to which the specified incoming parameter to the mocked method + * must be greater than. + * @return {@code 0}. The return value is always ignored. + */ + public static float gt(float expectedValue) { + return EasyMock.gt(expectedValue); + } + + /** + * Expects an {@code int} argument greater than the given value as a parameter + * to a mocked method. + * + * Use this to loosen the expectations of acceptable parameters for a mocked method call. + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.gt(42))).andReturn("hello");} + * + * @param expectedValue the value to which the specified incoming parameter to the mocked method + * must be greater than. + * @return {@code 0}. The return value is always ignored. + */ + public static int gt(int expectedValue) { + return EasyMock.gt(expectedValue); + } + + /** + * Expects a {@code long} argument greater than the given value as a parameter + * to a mocked method. + * + * Use this to loosen the expectations of acceptable parameters for a mocked method call. + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.gt(42l))).andReturn("hello");} + * + * @param expectedValue the value to which the specified incoming parameter to the mocked method + * must be greater than. + * @return {@code 0}. The return value is always ignored. + */ + public static long gt(long expectedValue) { + return EasyMock.gt(expectedValue); + } + + /** + * Expects a {@code short} argument greater than the given value as a parameter + * to a mocked method. + * + * Use this to loosen the expectations of acceptable parameters for a mocked method call. + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.gt((short)42))).andReturn("hello");} + * + * @param expectedValue the value to which the specified incoming parameter to the mocked method + * must be greater than. + * @return {@code 0}. The return value is always ignored. + */ + public static short gt(short expectedValue) { + return EasyMock.gt(expectedValue); + } + + /** + * Expects a {@code Comparable} argument less than the given value as a parameter + * to a mocked method. + * + * Use this to loosen the expectations of acceptable parameters for a mocked method call. + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.lt("hi"))).andReturn("hello");} + * + * @param expectedValue the value to which the specified incoming parameter to the mocked method + * must be less than. + * @return {@code null}. The return value is always ignored. + */ + public static <T extends Comparable<T>> T lt(Comparable<T> expectedValue) { + return EasyMock.lt(expectedValue); + } + + /** + * Expects a {@code byte} argument less than the given value as a parameter + * to a mocked method. + * + * Use this to loosen the expectations of acceptable parameters for a mocked method call. + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.lt((byte)42))).andReturn("hello");} + * + * @param expectedValue the value to which the specified incoming parameter to the mocked method + * must be less than. + * @return {@code 0}. The return value is always ignored. + */ + public static byte lt(byte expectedValue) { + return EasyMock.lt(expectedValue); + } + + /** + * Expects a {@code double} argument less than the given value as a parameter + * to a mocked method. + * + * Use this to loosen the expectations of acceptable parameters for a mocked method call. + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.lt(42.0))).andReturn("hello");} + * + * @param expectedValue the value to which the specified incoming parameter to the mocked method + * must be less than. + * @return {@code 0}. The return value is always ignored. + */ + public static double lt(double expectedValue) { + return EasyMock.lt(expectedValue); + } + + /** + * Expects a {@code float} argument less than the given value as a parameter + * to a mocked method. + * + * Use this to loosen the expectations of acceptable parameters for a mocked method call. + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.lt(42.0f))).andReturn("hello");} + * + * @param expectedValue the value to which the specified incoming parameter to the mocked method + * must be less than. + * @return {@code 0}. The return value is always ignored. + */ + public static float lt(float expectedValue) { + return EasyMock.lt(expectedValue); + } + + /** + * Expects an {@code int} argument less than the given value as a parameter + * to a mocked method. + * + * Use this to loosen the expectations of acceptable parameters for a mocked method call. + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.lt(42))).andReturn("hello");} + * + * @param expectedValue the value to which the specified incoming parameter to the mocked method + * must be less than. + * @return {@code 0}. The return value is always ignored. + */ + public static int lt(int expectedValue) { + return EasyMock.lt(expectedValue); + } + + /** + * Expects a {@code long} argument less than the given value as a parameter + * to a mocked method. + * + * Use this to loosen the expectations of acceptable parameters for a mocked method call. + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.lt(42l))).andReturn("hello");} + * + * @param expectedValue the value to which the specified incoming parameter to the mocked method + * must be less than. + * @return {@code 0}. The return value is always ignored. + */ + public static long lt(long expectedValue) { + return EasyMock.lt(expectedValue); + } + + /** + * Expects a {@code short} argument less than the given value as a parameter + * to a mocked method. + * + * Use this to loosen the expectations of acceptable parameters for a mocked method call. + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.lt((short)42))).andReturn("hello");} + * + * @param expectedValue the value to which the specified incoming parameter to the mocked method + * must be less than. + * @return {@code 0}. The return value is always ignored. + */ + public static short lt(short expectedValue) { + return EasyMock.lt(expectedValue); + } + + /** + * Expects an object implementing the given class as a parameter to a mocked method. During + * replay mode, the mocked method call will accept any {@code Object} that is an instance of + * the specified class or one of its subclasses. Specifically, any {@code non-null} parameter for + * which the {@code java.lang.Class.isAssignableFrom(Class)} will return true will be accepted by + * this matcher during the replay phase. + * + * Use this to loosen the expectations of acceptable parameters for a mocked method call. + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.isA(HashMap.class))).andReturn("hello");} + * + * @param <T> the expected Class type. + * @param clazz the class of the accepted type. + * @return {@code null}. The return value is always ignored. + */ + public static <T> T isA(Class<T> clazz) { + return EasyMock.isA(clazz); + } + + /** + * Expects a string that contains the given substring as a parameter to a mocked method. + * During replay mode, the mocked method will accept any {@code non-null String} which contains + * the provided {@code substring}. + * + * Use this to loosen the expectations of acceptable parameters for a mocked method call. + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.substring("hi"))).andReturn("hello");} + * + * @param substring the substring which any incoming parameter to the mocked method must contain. + * @return {@code null}. + */ + public static String contains(String substring) { + return EasyMock.contains(substring); + } + + /** + * Expects a {@code boolean} parameter that matches both of the provided expectations. During + * replay mode, the mocked method will accept any {@code boolean} that matches both of the + * provided expectations. Possible expectations for {@code first} and {@code second} include (but + * are not limited to) {@link #anyBoolean()} and {@link #eq(boolean)}. + * + * E.g. + * {@code AndroidMock.expect(mock.getString( + * AndroidMock.and(AndroidMock.anyBoolean(), AndroidMock.eq(true)))).andReturn("hello");} + * + * Or, for illustration purposes (using static imports) + * + * {@code expect(mock.getString(and(anyBoolean(), eq(true)))).andReturn("hello");} + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * @param first the first expectation to test. + * @param second the second expectation to test. + * @return {@code false}. The return value is always ignored. + */ + public static boolean and(boolean first, boolean second) { + return EasyMock.and(first, second); + } + + /** + * Expects a {@code byte} parameter that matches both of the provided expectations. During replay + * mode, the mocked method will accept any {@code byte} that matches both of the provided + * expectations. Possible expectations for {@code first} and {@code second} include (but are not + * limited to) {@link #anyByte()}, {@link #leq(byte)} and {@link #eq(byte)}. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.and( + * AndroidMock.gt((byte)0), AndroidMock.lt((byte)42)))).andReturn("hello");} + * + * Or, for illustration purposes (using static imports) + * + * {@code expect(mock.getString(and(gt((byte)0), lt((byte)42)))).andReturn("hello");} + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * @param first the first expectation to test. + * @param second the second expectation to test. + * @return {@code 0}. The return value is always ignored. + */ + public static byte and(byte first, byte second) { + return EasyMock.and(first, second); + } + + /** + * Expects a {@code char} parameter that matches both of the provided expectations. During replay + * mode, the mocked method will accept any {@code char} that matches both of the provided + * expectations. Possible expectations for {@code first} and {@code second} include (but are not + * limited to) {@link #anyChar()} and {@link #eq(char)}. + * + * E.g. + * {@code AndroidMock.expect(mock.getString( + * AndroidMock.and(AndroidMock.geq('a'), AndroidMock.lt('q')))).andReturn("hello");} + * + * Or, for illustration purposes (using static imports) + * + * {@code expect(mock.getString(and(eq('a'), anyChar()))).andReturn("hello");} + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * @param first the first expectation to test. + * @param second the second expectation to test. + * @return {@code 0}. The return value is always ignored. + */ + public static char and(char first, char second) { + return EasyMock.and(first, second); + } + + /** + * Expects a {@code double} parameter that matches both of the provided expectations. During + * replay mode, the mocked method will accept any {@code double} that matches both of the provided + * expectations. Possible expectations for {@code first} and {@code second} include (but are not + * limited to) {@link #anyDouble()}, {@link #leq(double)} and {@link #eq(double)}. + * + * E.g. + * {@code AndroidMock.expect(mock.getString( + * AndroidMock.and(AndroidMock.gt(0.0), AndroidMock.lt(42.0)))).andReturn("hello");} + * + * Or, for illustration purposes (using static imports) + * + * {@code expect(mock.getString(and(gt(0.0), lt(42.0)))).andReturn("hello");} + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * @param first the first expectation to test. + * @param second the second expectation to test. + * @return {@code 0}. The return value is always ignored. + */ + public static double and(double first, double second) { + return EasyMock.and(first, second); + } + + /** + * Expects a {@code float} parameter that matches both of the provided expectations. During + * replay mode, the mocked method will accept any {@code float} that matches both of the provided + * expectations. Possible expectations for {@code first} and {@code second} include (but are not + * limited to) {@link #anyFloat()}, {@link #leq(float)} and {@link #eq(float)}. + * + * E.g. + * {@code AndroidMock.expect(mock.getString( + * AndroidMock.and(AndroidMock.gt(0.0f), AndroidMock.lt(42.0f)))).andReturn("hello");} + * + * Or, for illustration purposes (using static imports) + * + * {@code expect(mock.getString(and(gt(0.0f), lt(42.0f)))).andReturn("hello");} + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * @param first the first expectation to test. + * @param second the second expectation to test. + * @return {@code 0}. The return value is always ignored. + */ + public static float and(float first, float second) { + return EasyMock.and(first, second); + } + + /** + * Expects an {@code int} parameter that matches both of the provided expectations. During + * replay mode, the mocked method will accept any {@code int} that matches both of the provided + * expectations. Possible expectations for {@code first} and {@code second} include (but are not + * limited to) {@link #anyInt()}, {@link #leq(int)} and {@link #eq(int)}. + * + * E.g. + * {@code AndroidMock.expect(mock.getString( + * AndroidMock.and(AndroidMock.gt(0), AndroidMock.lt(42)))).andReturn("hello");} + * + * Or, for illustration purposes (using static imports) + * + * {@code expect(mock.getString(and(gt(0), lt(42)))).andReturn("hello");} + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * @param first the first expectation to test. + * @param second the second expectation to test. + * @return {@code 0}. The return value is always ignored. + */ + public static int and(int first, int second) { + return EasyMock.and(first, second); + } + + /** + * Expects a {@code long} parameter that matches both of the provided expectations. During + * replay mode, the mocked method will accept any {@code long} that matches both of the provided + * expectations. Possible expectations for {@code first} and {@code second} include (but are not + * limited to) {@link #anyLong()}, {@link #leq(long)} and {@link #eq(long)}. + * + * E.g. + * {@code AndroidMock.expect(mock.getString( + * AndroidMock.and(AndroidMock.gt(0l), AndroidMock.lt(42l)))).andReturn("hello");} + * + * Or, for illustration purposes (using static imports) + * + * {@code expect(mock.getString(and(gt(0l), lt(42l)))).andReturn("hello");} + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * @param first the first expectation to test. + * @param second the second expectation to test. + * @return {@code 0}. The return value is always ignored. + */ + public static long and(long first, long second) { + return EasyMock.and(first, second); + } + + /** + * Expects a {@code short} parameter that matches both of the provided expectations. During + * replay mode, the mocked method will accept any {@code short} that matches both of the provided + * expectations. Possible expectations for {@code first} and {@code second} include (but are not + * limited to) {@link #anyShort()}, {@link #leq(short)} and {@link #eq(short)}. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.and( + * AndroidMock.gt((short)0), AndroidMock.lt((short)42)))).andReturn("hello");} + * + * Or, for illustration purposes (using static imports) + * + * {@code expect(mock.getString(and(gt((short)0), lt((short)42)))).andReturn("hello");} + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * @param first the first expectation to test. + * @param second the second expectation to test. + * @return {@code 0}. The return value is always ignored. + */ + public static short and(short first, short second) { + return EasyMock.and(first, second); + } + + /** + * Expects an {@code Object} parameter that matches both of the provided expectations. During + * replay mode, the mocked method will accept any {@code Object} that matches both of the provided + * expectations. Possible expectations for {@code first} and {@code second} include (but are not + * limited to) {@link #anyObject()}, {@link #isA(Class)} and {@link #contains(String)}. + * + * E.g. + * {@code AndroidMock.expect(mock.getString( + * AndroidMock.and( + * AndroidMock.contains("hi"), AndroidMock.contains("world")))).andReturn("hello");} + * + * Or, for illustration purposes (using static imports) + * + * {@code expect(mock.getString(and(contains("hi"), contains("world")))).andReturn("hello");} + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * @param first the first expectation to test. + * @param second the second expectation to test. + * @return {@code 0}. The return value is always ignored. + */ + public static <T> T and(T first, T second) { + return EasyMock.and(first, second); + } + + /** + * Expects a {@code boolean} parameter that matches one or both of the provided expectations. + * During replay mode, the mocked method will accept any {@code boolean} that matches one of the + * provided expectations, or both of them. Possible expectations for {@code first} and + * {@code second} include (but are not limited to) {@link #anyBoolean()} and {@link #eq(boolean)}. + * + * E.g. + * {@code AndroidMock.expect(mock.getString( + * AndroidMock.or(AndroidMock.eq(true), AndroidMock.anyBoolean()))).andReturn("hello");} + * + * Or, for illustration purposes (using static imports) + * + * {@code expect(mock.getString(and(eq(true), anyBoolean()))).andReturn("hello");} + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * @param first the first expectation to test. + * @param second the second expectation to test. + * @return {@code false}. The return value is always ignored. + */ + public static boolean or(boolean first, boolean second) { + return EasyMock.or(first, second); + } + + /** + * Expects a {@code byte} parameter that matches one or both of the provided expectations. + * During replay mode, the mocked method will accept any {@code byte} that matches one of the + * provided expectations, or both of them. Possible expectations for {@code first} and + * {@code second} include (but are not limited to) {@link #anyByte()}, {@link #eq(byte)}, + * and {@link #lt(byte)}. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.or( + * AndroidMock.geq((byte)0), AndroidMock.lt((byte)42)))).andReturn("hello");} + * + * Or, for illustration purposes (using static imports) + * + * {@code expect(mock.getString(or(geq((byte)0), lt((byte)42)))).andReturn("hello");} + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * @param first the first expectation to test. + * @param second the second expectation to test. + * @return {@code 0}. The return value is always ignored. + */ + public static byte or(byte first, byte second) { + return EasyMock.or(first, second); + } + + /** + * Expects a {@code char} parameter that matches one or both of the provided expectations. + * During replay mode, the mocked method will accept any {@code char} that matches one of the + * provided expectations, or both of them. Possible expectations for {@code first} and + * {@code second} include (but are not limited to) {@link #anyChar()} and {@link #eq(char)}. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.or( + * AndroidMock.eq('a'), AndroidMock.eq('z')))).andReturn("hello");} + * + * Or, for illustration purposes (using static imports) + * + * {@code expect(mock.getString(or(eq('a'), eq('z')))).andReturn("hello");} + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * @param first the first expectation to test. + * @param second the second expectation to test. + * @return {@code 0}. The return value is always ignored. + */ + public static char or(char first, char second) { + return EasyMock.or(first, second); + } + + /** + * Expects a {@code double} parameter that matches one or both of the provided expectations. + * During replay mode, the mocked method will accept any {@code double} that matches one of the + * provided expectations, or both of them. Possible expectations for {@code first} and + * {@code second} include (but are not limited to) {@link #anyDouble()}, {@link #eq(double)} + * and {@link #lt(double)}. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.or( + * AndroidMock.eq(0.0), AndroidMock.geq(42.0)))).andReturn("hello");} + * + * Or, for illustration purposes (using static imports) + * + * {@code expect(mock.getString(or(eq(0.0), geq(42.0)))).andReturn("hello");} + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * @param first the first expectation to test. + * @param second the second expectation to test. + * @return {@code 0}. The return value is always ignored. + */ + public static double or(double first, double second) { + return EasyMock.or(first, second); + } + + /** + * Expects a {@code float} parameter that matches one or both of the provided expectations. + * During replay mode, the mocked method will accept any {@code float} that matches one of the + * provided expectations, or both of them. Possible expectations for {@code first} and + * {@code second} include (but are not limited to) {@link #anyFloat()}, {@link #eq(float)} + * and {@link #lt(float)}. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.or( + * AndroidMock.eq(0.0f), AndroidMock.geq(42.0f)))).andReturn("hello");} + * + * Or, for illustration purposes (using static imports) + * + * {@code expect(mock.getString(or(eq(0.0f), geq(42.0f)))).andReturn("hello");} + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * @param first the first expectation to test. + * @param second the second expectation to test. + * @return {@code 0}. The return value is always ignored. + */ + public static float or(float first, float second) { + return EasyMock.or(first, second); + } + + /** + * Expects an {@code int} parameter that matches one or both of the provided expectations. + * During replay mode, the mocked method will accept any {@code int} that matches one of the + * provided expectations, or both of them. Possible expectations for {@code first} and + * {@code second} include (but are not limited to) {@link #anyInt()}, {@link #eq(int)} + * and {@link #lt(int)}. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.or( + * AndroidMock.eq(0), AndroidMock.geq(42)))).andReturn("hello");} + * + * Or, for illustration purposes (using static imports) + * + * {@code expect(mock.getString(or(eq(0), geq(42)))).andReturn("hello");} + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * @param first the first expectation to test. + * @param second the second expectation to test. + * @return {@code 0}. The return value is always ignored. + */ + public static int or(int first, int second) { + return EasyMock.or(first, second); + } + + /** + * Expects a {@code long} parameter that matches one or both of the provided expectations. + * During replay mode, the mocked method will accept any {@code long} that matches one of the + * provided expectations, or both of them. Possible expectations for {@code first} and + * {@code second} include (but are not limited to) {@link #anyLong()}, {@link #eq(long)} + * and {@link #lt(long)}. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.or( + * AndroidMock.eq(0l), AndroidMock.geq(42l)))).andReturn("hello");} + * + * Or, for illustration purposes (using static imports) + * + * {@code expect(mock.getString(or(eq(0l), geq(42l)))).andReturn("hello");} + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * @param first the first expectation to test. + * @param second the second expectation to test. + * @return {@code 0}. The return value is always ignored. + */ + public static long or(long first, long second) { + return EasyMock.or(first, second); + } + + /** + * Expects a {@code short} parameter that matches one or both of the provided expectations. + * During replay mode, the mocked method will accept any {@code short} that matches one of the + * provided expectations, or both of them. Possible expectations for {@code first} and + * {@code second} include (but are not limited to) {@link #anyShort()}, {@link #eq(short)} + * and {@link #lt(short)}. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.or( + * AndroidMock.eq((short)0), AndroidMock.geq((short)42)))).andReturn("hello");} + * + * Or, for illustration purposes (using static imports) + * + * {@code expect(mock.getString(or(eq((short)0), geq((short)42)))).andReturn("hello");} + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * @param first the first expectation to test. + * @param second the second expectation to test. + * @return {@code 0}. The return value is always ignored. + */ + public static short or(short first, short second) { + return EasyMock.or(first, second); + } + + /** + * Expects an {@code Object} parameter that matches one or both of the provided expectations. + * During replay mode, the mocked method will accept any {@code Object} that matches one of the + * provided expectations, or both of them. Possible expectations for {@code first} and + * {@code second} include (but are not limited to) {@link #anyObject()}, {@link #eq(Class)} + * and {@link #lt(Comparable)}. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.or( + * AndroidMock.notNull(), AndroidMock.geq(fortyTwo)))).andReturn("hello");} + * + * Or, for illustration purposes (using static imports) + * + * {@code expect(mock.getString(or(notNull(), geq(fortyTwo)))).andReturn("hello");} + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * @param first the first expectation to test. + * @param second the second expectation to test. + * @return {@code null}. The return value is always ignored. + */ + public static <T> T or(T first, T second) { + return EasyMock.or(first, second); + } + + /** + * Expects a {@code boolean} parameter that does not match the provided expectation. + * During replay mode, the mocked method will accept any {@code boolean} that does not match + * the provided expectation. Possible expectations for {@code expectation} + * include (but are not limited to) {@link #anyBoolean()} and {@link #eq(boolean)}. + * + * E.g. + * {@code AndroidMock.expect(mock.getString( + * AndroidMock.not(AndroidMock.eq(true)))).andReturn("hello");} + * + * Or, for illustration purposes (using static imports) + * + * {@code expect(mock.getString(not(eq(true)))).andReturn("hello");} + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * @param expectation the expectation to test. + * @return {@code false}. The return value is always ignored. + */ + public static boolean not(boolean expectation) { + return EasyMock.not(expectation); + } + + /** + * Expects a {@code byte} parameter that does not match the provided expectation. + * During replay mode, the mocked method will accept any {@code byte} that does not match + * the provided expectation. Possible expectations for {@code expectation} + * include (but are not limited to) {@link #anyByte()}, {@link #eq(byte)} and + * {@link #lt(byte)}. + * + * E.g. + * {@code AndroidMock.expect(mock.getString( + * AndroidMock.not(AndroidMock.eq((byte)42)))).andReturn("hello");} + * + * Or, for illustration purposes (using static imports) + * + * {@code expect(mock.getString(not(eq((byte)42)))).andReturn("hello");} + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * @param expectation the expectation to test. + * @return {@code 0}. The return value is always ignored. + */ + public static byte not(byte expectation) { + return EasyMock.not(expectation); + } + + /** + * Expects a {@code char} parameter that does not match the provided expectation. + * During replay mode, the mocked method will accept any {@code char} that does not match + * the provided expectation. Possible expectations for {@code expectation} + * include (but are not limited to) {@link #anyChar()} and {@link #eq(char)}. + * + * E.g. + * {@code AndroidMock.expect(mock.getString( + * AndroidMock.not(AndroidMock.eq('a')))).andReturn("hello");} + * + * Or, for illustration purposes (using static imports) + * + * {@code expect(mock.getString(not(eq('a')))).andReturn("hello");} + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * @param expectation the expectation to test. + * @return {@code 0}. The return value is always ignored. + */ + public static char not(char expectation) { + return EasyMock.not(expectation); + } + + /** + * Expects a {@code double} parameter that does not match the provided expectation. + * During replay mode, the mocked method will accept any {@code double} that does not match + * the provided expectation. Possible expectations for {@code expectation} + * include (but are not limited to) {@link #anyDouble()}, {@link #eq(double)} and + * {@link #lt(double)}. + * + * E.g. + * {@code AndroidMock.expect(mock.getString( + * AndroidMock.not(AndroidMock.eq(42.0)))).andReturn("hello");} + * + * Or, for illustration purposes (using static imports) + * + * {@code expect(mock.getString(not(eq(42.0)))).andReturn("hello");} + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * @param expectation the expectation to test. + * @return {@code 0}. The return value is always ignored. + */ + public static double not(double expectation) { + return EasyMock.not(expectation); + } + + /** + * Expects a {@code float} parameter that does not match the provided expectation. + * During replay mode, the mocked method will accept any {@code float} that does not match + * the provided expectation. Possible expectations for {@code expectation} + * include (but are not limited to) {@link #anyFloat()}, {@link #eq(float)} and + * {@link #lt(float)}. + * + * E.g. + * {@code AndroidMock.expect(mock.getString( + * AndroidMock.not(AndroidMock.eq(42.0f)))).andReturn("hello");} + * + * Or, for illustration purposes (using static imports) + * + * {@code expect(mock.getString(not(eq(42.0f)))).andReturn("hello");} + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * @param expectation the expectation to test. + * @return {@code 0}. The return value is always ignored. + */ + public static float not(float expectation) { + return EasyMock.not(expectation); + } + + /** + * Expects a {@code int} parameter that does not match the provided expectation. + * During replay mode, the mocked method will accept any {@code int} that does not match + * the provided expectation. Possible expectations for {@code expectation} + * include (but are not limited to) {@link #anyInt()}, {@link #eq(int)} and + * {@link #lt(int)}. + * + * E.g. + * {@code AndroidMock.expect(mock.getString( + * AndroidMock.not(AndroidMock.eq(42)))).andReturn("hello");} + * + * Or, for illustration purposes (using static imports) + * + * {@code expect(mock.getString(not(eq(42)))).andReturn("hello");} + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * @param expectation the expectation to test. + * @return {@code 0}. The return value is always ignored. + */ + public static int not(int expectation) { + return EasyMock.not(expectation); + } + + /** + * Expects a {@code long} parameter that does not match the provided expectation. + * During replay mode, the mocked method will accept any {@code long} that does not match + * the provided expectation. Possible expectations for {@code expectation} + * include (but are not limited to) {@link #anyLong()}, {@link #eq(long)} and + * {@link #lt(long)}. + * + * E.g. + * {@code AndroidMock.expect(mock.getString( + * AndroidMock.not(AndroidMock.eq(42l)))).andReturn("hello");} + * + * Or, for illustration purposes (using static imports) + * + * {@code expect(mock.getString(not(eq(42l)))).andReturn("hello");} + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * @param expectation the expectation to test. + * @return {@code 0}. The return value is always ignored. + */ + public static long not(long expectation) { + return EasyMock.not(expectation); + } + + /** + * Expects a {@code short} parameter that does not match the provided expectation. + * During replay mode, the mocked method will accept any {@code short} that does not match + * the provided expectation. Possible expectations for {@code expectation} + * include (but are not limited to) {@link #anyShort()}, {@link #eq(short)} and + * {@link #lt(short)}. + * + * E.g. + * {@code AndroidMock.expect(mock.getString( + * AndroidMock.not(AndroidMock.eq((short)42)))).andReturn("hello");} + * + * Or, for illustration purposes (using static imports) + * + * {@code expect(mock.getString(not(eq((short)42)))).andReturn("hello");} + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * @param expectation the expectation to test. + * @return {@code 0}. The return value is always ignored. + */ + public static short not(short expectation) { + return EasyMock.not(expectation); + } + + /** + * Expects an {@code Object} parameter that does not match the given expectation. + * During replay mode, the mocked method will accept any {@code Object} that does not match + * the provided expectation. Possible expectations for {@code expectation} + * include (but are not limited to) {@link #anyObject()}, {@link #leq(Comparable)} and + * {@link #isNull()}. + * + * E.g. + * {@code AndroidMock.expect(mock.getString( + * AndroidMock.not(AndroidMock.eq(fortyTwo)))).andReturn("hello");} + * + * Or, for illustration purposes (using static imports) + * + * {@code expect(mock.getString(not(eq(fortyTwo)))).andReturn("hello");} + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * @param expectation the expectation to test. + * @return {@code 0}. The return value is always ignored. + */ + public static <T> T not(T expectation) { + return EasyMock.not(expectation); + } + + /** + * Expects a {@code boolean} parameter that is equal to the provided {@code value}. + * During replay mode, the mocked method will accept any {@code boolean} that matches the + * value of {@code expectedValue}. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.eq(true))).andReturn("hello");} + * + * Or, for illustration purposes (using static imports) + * + * {@code expect(mock.getString(eq(true))).andReturn("hello");} + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * @param expectedValue the value to which the specified incoming parameter to the mocked method + * must be equal. + * @return {@code false}. The return value is always ignored. + */ + public static boolean eq(boolean expectedValue) { + return EasyMock.eq(expectedValue); + } + + /** + * Expects a {@code byte} parameter that is equal to the provided {@code expectedValue}. + * During replay mode, the mocked method will accept any {@code byte} that matches the + * value of {@code expectedValue}. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.eq((byte)0))).andReturn("hello");} + * + * Or, for illustration purposes (using static imports) + * + * {@code expect(mock.getString(eq((byte)0))).andReturn("hello");} + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * @param expectedValue the value to which the specified incoming parameter to the mocked method + * must be equal. + * @return {@code false}. The return value is always ignored. + */ + public static byte eq(byte expectedValue) { + return EasyMock.eq(expectedValue); + } + + /** + * Expects a {@code char} parameter that is equal to the provided {@code expectedValue}. + * During replay mode, the mocked method will accept any {@code char} that matches the + * value of {@code expectedValue}. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.eq('a'))).andReturn("hello");} + * + * Or, for illustration purposes (using static imports) + * + * {@code expect(mock.getString(eq('a'))).andReturn("hello");} + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * @param expectedValue the value to which the specified incoming parameter to the mocked method + * must be equal. + * @return {@code 0}. The return value is always ignored. + */ + public static char eq(char expectedValue) { + return EasyMock.eq(expectedValue); + } + + /** + * Expects a {@code double} parameter that is equal to the provided {@code expectedValue}. + * During replay mode, the mocked method will accept any {@code double} that matches the + * value of {@code expectedValue}. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.eq(0.0))).andReturn("hello");} + * + * Or, for illustration purposes (using static imports) + * + * {@code expect(mock.getString(eq(0.0))).andReturn("hello");} + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * @param expectedValue the value to which the specified incoming parameter to the mocked method + * must be equal. + * @return {@code 0}. The return value is always ignored. + */ + public static double eq(double expectedValue) { + return EasyMock.eq(expectedValue); + } + + /** + * Expects a {@code float} parameter that is equal to the provided {@code expectedValue}. + * During replay mode, the mocked method will accept any {@code float} that matches the + * value of {@code expectedValue}. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.eq(0.0f))).andReturn("hello");} + * + * Or, for illustration purposes (using static imports) + * + * {@code expect(mock.getString(eq(0.0f))).andReturn("hello");} + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * @param expectedValue the value to which the specified incoming parameter to the mocked method + * must be equal. + * @return {@code 0}. The return value is always ignored. + */ + public static float eq(float expectedValue) { + return EasyMock.eq(expectedValue); + } + + /** + * Expects an {@code int} parameter that is equal to the provided {@code expectedValue}. + * During replay mode, the mocked method will accept any {@code int} that matches the + * value of {@code expectedValue}. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.eq(0))).andReturn("hello");} + * + * Or, for illustration purposes (using static imports) + * + * {@code expect(mock.getString(eq(0))).andReturn("hello");} + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * @param expectedValue the value to which the specified incoming parameter to the mocked method + * must be equal. + * @return {@code 0}. The return value is always ignored. + */ + public static int eq(int expectedValue) { + return EasyMock.eq(expectedValue); + } + + /** + * Expects a {@code long} parameter that is equal to the provided {@code expectedValue}. + * During replay mode, the mocked method will accept any {@code long} that matches the + * value of {@code expectedValue}. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.eq(0l))).andReturn("hello");} + * + * Or, for illustration purposes (using static imports) + * + * {@code expect(mock.getString(eq(0l))).andReturn("hello");} + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * @param expectedValue the value to which the specified incoming parameter to the mocked method + * must be equal. + * @return {@code 0}. The return value is always ignored. + */ + public static long eq(long expectedValue) { + return EasyMock.eq(expectedValue); + } + + /** + * Expects a {@code short} parameter that is equal to the provided {@code expectedValue}. + * During replay mode, the mocked method will accept any {@code short} that matches the + * value of {@code expectedValue}. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.eq((short)0))).andReturn("hello");} + * + * Or, for illustration purposes (using static imports) + * + * {@code expect(mock.getString(eq((short)0))).andReturn("hello");} + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * @param expectedValue the value to which the specified incoming parameter to the mocked method + * must be equal. + * @return {@code 0}. The return value is always ignored. + */ + public static short eq(short expectedValue) { + return EasyMock.eq(expectedValue); + } + + /** + * Expects an {@code Object} parameter that is equal to the provided {@code expectedValue}. + * During replay mode, the mocked method will accept any {@code Object} that matches the + * value of {@code expectedValue} according to its {@code equals(Object)} method. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.eq("hi"))).andReturn("hello");} + * + * Or, for illustration purposes (using static imports) + * + * {@code expect(mock.getString(eq("hi"))).andReturn("hello");} + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * @param expectedValue the value to which the specified incoming parameter to the mocked method + * must be equal. + * @return {@code 0}. The return value is always ignored. + */ + public static <T> T eq(T expectedValue) { + return EasyMock.eq(expectedValue); + } + + /** + * Expects a {@code boolean} array parameter that is equal to the given array, i.e. it has to + * have the same length, and each element has to be equal. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.eq(myBooleanArray))).andReturn("hello");} + * + * Or, for illustration purposes (using static imports) + * + * {@code expect(mock.getString(eq(myBooleanArray))).andReturn("hello");} + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * @param expectedValue the array to which the specified incoming parameter to the mocked method + * must have equal contents. + * @return {@code null}. The return value is always ignored. + */ + public static boolean[] aryEq(boolean[] expectedValue) { + return EasyMock.aryEq(expectedValue); + } + + /** + * Expects a {@code byte} array parameter that is equal to the given array, i.e. it has to + * have the same length, and each element has to be equal. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.eq(myByteArray))).andReturn("hello");} + * + * Or, for illustration purposes (using static imports) + * + * {@code expect(mock.getString(eq(myByteArray))).andReturn("hello");} + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * @param expectedValue the array to which the specified incoming parameter to the mocked method + * must have equal contents. + * @return {@code null}. The return value is always ignored. + */ + public static byte[] aryEq(byte[] expectedValue) { + return EasyMock.aryEq(expectedValue); + } + + /** + * Expects a {@code char} array parameter that is equal to the given array, i.e. it has to + * have the same length, and each element has to be equal. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.eq(myCharArray))).andReturn("hello");} + * + * Or, for illustration purposes (using static imports) + * + * {@code expect(mock.getString(eq(myCharArray))).andReturn("hello");} + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * @param expectedValue the array to which the specified incoming parameter to the mocked method + * must have equal contents. + * @return {@code null}. The return value is always ignored. + */ + public static char[] aryEq(char[] expectedValue) { + return EasyMock.aryEq(expectedValue); + } + + /** + * Expects a {@code double} array parameter that is equal to the given array, i.e. it has to + * have the same length, and each element has to be equal. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.eq(myDoubleArray))).andReturn("hello");} + * + * Or, for illustration purposes (using static imports) + * + * {@code expect(mock.getString(eq(myDoubleArray))).andReturn("hello");} + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * @param expectedValue the array to which the specified incoming parameter to the mocked method + * must have equal contents. + * @return {@code null}. The return value is always ignored. + */ + public static double[] aryEq(double[] expectedValue) { + return EasyMock.aryEq(expectedValue); + } + + /** + * Expects a {@code float} array parameter that is equal to the given array, i.e. it has to + * have the same length, and each element has to be equal. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.eq(myFloatrArray))).andReturn("hello");} + * + * Or, for illustration purposes (using static imports) + * + * {@code expect(mock.getString(eq(myFloatArray))).andReturn("hello");} + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * @param expectedValue the array to which the specified incoming parameter to the mocked method + * must have equal contents. + * @return {@code null}. The return value is always ignored. + */ + public static float[] aryEq(float[] expectedValue) { + return EasyMock.aryEq(expectedValue); + } + + /** + * Expects an {@code int} array parameter that is equal to the given array, i.e. it has to + * have the same length, and each element has to be equal. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.eq(myIntArray))).andReturn("hello");} + * + * Or, for illustration purposes (using static imports) + * + * {@code expect(mock.getString(eq(myIntArray))).andReturn("hello");} + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * @param expectedValue the array to which the specified incoming parameter to the mocked method + * must have equal contents. + * @return {@code null}. The return value is always ignored. + */ + public static int[] aryEq(int[] expectedValue) { + return EasyMock.aryEq(expectedValue); + } + + /** + * Expects a {@code long} array parameter that is equal to the given array, i.e. it has to + * have the same length, and each element has to be equal. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.eq(myLongArray))).andReturn("hello");} + * + * Or, for illustration purposes (using static imports) + * + * {@code expect(mock.getString(eq(myLongArray))).andReturn("hello");} + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * @param expectedValue the array to which the specified incoming parameter to the mocked method + * must have equal contents. + * @return {@code null}. The return value is always ignored. + */ + public static long[] aryEq(long[] expectedValue) { + return EasyMock.aryEq(expectedValue); + } + + /** + * Expects a {@code short} array parameter that is equal to the given array, i.e. it has to + * have the same length, and each element has to be equal. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.eq(myShortArray))).andReturn("hello");} + * + * Or, for illustration purposes (using static imports) + * + * {@code expect(mock.getString(eq(myShortArray))).andReturn("hello");} + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * @param expectedValue the array to which the specified incoming parameter to the mocked method + * must have equal contents. + * @return {@code null}. The return value is always ignored. + */ + public static short[] aryEq(short[] expectedValue) { + return EasyMock.aryEq(expectedValue); + } + + /** + * Expects a {@code Object} array parameter that is equal to the given array, i.e. it has to + * have the same length, and each element has to be equal. + * + * E.g. + * {@code AndroidMock.expect(mock.getString(AndroidMock.eq(myObjectArray))).andReturn("hello");} + * + * Or, for illustration purposes (using static imports) + * + * {@code expect(mock.getString(eq(myObjectArray))).andReturn("hello");} + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * @param <T> the type of the array, it is passed through to prevent casts. + * @param expectedValue the array to which the specified incoming parameter to the mocked method + * must have equal contents. + * @return {@code null}. The return value is always ignored. + */ + public static <T> T[] aryEq(T[] expectedValue) { + return EasyMock.aryEq(expectedValue); + } + + /** + * Expects any {@code null} Object as a parameter. + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * @return {@code null}. The return value is always ignored. + */ + @SuppressWarnings("unchecked") + public static <T> T isNull() { + return (T) EasyMock.isNull(); + } + + /** + * Expects any {@code non-null} Object parameter. + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * @return {@code null}. The return value is always ignored. + */ + @SuppressWarnings("unchecked") + public static <T> T notNull() { + return (T) EasyMock.notNull(); + } + + /** + * Expects a {@code String} that contains a substring that matches the given regular + * expression as a parameter to the mocked method. + * + * See {@link java.util.regex.Matcher#find()} for more details. + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * @param regex the regular expression which must match some substring of the incoming parameter + * to the mocked method. + * @return {@code null}. The return value is always ignored. + */ + public static String find(String regex) { + return EasyMock.find(regex); + } + + /** + * Expects a {@code String} as a parameter to the mocked method, the entire length of which must + * match the given regular expression. This is not to be confused with {@link #find(String)} which + * matches the regular expression against any substring of the incoming parameter to the mocked + * method. + * + * See {@link java.util.regex.Matcher#matches()} for more details. + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * @param regex the regular expression against which the entire incoming parameter to the + * mocked method must match. + * @return {@code null}. The return value is always ignored. + */ + public static String matches(String regex) { + return EasyMock.matches(regex); + } + + /** + * Expects a {@code String} as a parameter to the mocked method that starts with the given prefix. + * + * See {@link java.lang.String#startsWith(String)} for more details. + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * @param prefix the string that is expected to match against the start of any incoming + * parameter to the mocked method. + * @return {@code null}. The return value is always ignored. + */ + public static String startsWith(String prefix) { + return EasyMock.startsWith(prefix); + } + + /** + * Expects a {@code String} as a parameter to the mocked method that ends with the given + * {@code suffix}. + * + * See {@link java.lang.String#startsWith(String)} for more details. + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * @param suffix the string that is expected to match against the end of any incoming + * parameter to the mocked method. + * @return {@code null}. The return value is always ignored. + */ + public static String endsWith(String suffix) { + return EasyMock.endsWith(suffix); + } + + /** + * Expects a {@code double} as a parameter to the mocked method that has an absolute difference to + * the given {@code expectedValue} that is less than the given {@code delta}. + * + * The acceptable range of values is theoretically defined as any value {@code x} which satisfies + * the following inequality: {@code expectedValue - delta <= x <= expectedValue + delta}. + * + * In practice, this is only true when {@code expectedValue + delta} and + * {@code expectedValue - delta} fall exactly on a precisely representable {@code double} value. + * Normally, the acceptable range of values is defined as any value {@code x} which satisfies the + * following inequality: + * {@code expectedValue - delta < x < expectedValue + delta}. + * + * E.g. {@code AndroidMock.expect(mockObject.getString( + * AndroidMock.eq(42.0, 0.1))).andReturn("hello world");} + * + * The code snippet above will expect any {@code double} value greater than 41.9 and + * less than 42.1. + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * @param expectedValue the center value of the expected range of values. + * @param delta the acceptable level of inaccuracy before this expectation fails. + * @return {@code 0}. The return value is always ignored. + */ + public static double eq(double expectedValue, double delta) { + return EasyMock.eq(expectedValue, delta); + } + + /** + * Expects a {@code float} as a parameter to the mocked method that has an absolute difference to + * the given {@code expectedValue} that is less than the given {@code delta}. + * + * The acceptable range of values is theoretically defined as any value {@code x} which satisfies + * the following inequality: {@code expectedValue - delta <= x <= expectedValue + delta}. + * + * In practice, this is only true when {@code expectedValue + delta} and + * {@code expectedValue - delta} fall exactly on a precisely representable {@code float} value. + * Normally, the acceptable range of values is defined as any value {@code x} which satisfies the + * following inequality: + * {@code expectedValue - delta < x < expectedValue + delta}. + * + * E.g. {@code AndroidMock.expect(mockObject.getString( + * AndroidMock.eq(42.0f, 0.1f))).andReturn("hello world");} + * + * The code snippet above will expect any {@code float} value greater than 41.9 and + * less than 42.1. + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * @param expectedValue the center value of the expected range of values. + * @param delta the acceptable level of inaccuracy before this expectation fails. + * @return {@code 0}. The return value is always ignored. + */ + public static float eq(float expectedValue, float delta) { + return EasyMock.eq(expectedValue, delta); + } + + /** + * Expects an {@code Object} as a parameter to the mocked method that is the same as the given + * value. This expectation will fail unless the incoming parameter is {@code ==} to the + * {@code expectedValue} provided (i.e. the same {@code Object} reference). + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * @param <T> the type of the object, it is passed through to prevent casts. + * @param expectedValue the exact object which is expected during replay. + * @return {@code null}. The return value is always ignored. + */ + public static <T> T same(T expectedValue) { + return EasyMock.same(expectedValue); + } + + /** + * Expects a {@link java.lang.Comparable} argument equal to the given value according to + * its {@link java.lang.Comparable#compareTo(Object)} method. + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * @param expectedValue the {@link java.lang.Comparable} value which is expected to be equal to + * the incoming parameter to the mocked method according to the + * {@link java.lang.Comparable#compareTo(Object)} method. + * @return {@code null}. The return value is always ignored. + */ + public static <T extends Comparable<T>> T cmpEq(Comparable<T> expectedValue) { + return EasyMock.cmpEq(expectedValue); + } + + /** + * Expects an argument that will be compared using the provided {@link java.util.Comparator}, the + * result of which will then be applied to the provided {@link org.easymock.LogicalOperator} + * (e.g. {@link org.easymock.LogicalOperator#LESS_THAN}, + * {@link org.easymock.LogicalOperator#EQUAL}, + * {@link org.easymock.LogicalOperator#GREATER_OR_EQUAL}). + * + * The following comparison will take place: + * {@code comparator.compare(actual, expected) operator 0} + * + * E.g. + * For illustration purposes (using static imports): + * + * {@code + * expect(mockObject.getString(cmp("hi", CASE_INSENSITIVE_ORDER, LESS_THAN))).andReturn("hello");} + * + * {@code + * AndroidMock.expect(mockObject.getString(AndroidMock.cmp("hi", String.CASE_INSENSITIVE_ORDER, + * LogicalOperator.LESS_THAN))).andReturn("hello");} + * + * + * The above invocation indicates that the call to {@code mockObject.getString(String)} is + * expecting any String which is lexically before "hi" (in a case insensitive ordering). + * + * If this method is used for anything other than to set a parameter expectation as part of a + * mock object's recording phase, then an {@code IllegalStateException} will be thrown. + * + * @param expectedValue the expected value against which the incoming method parameter will be + * compared. + * @param comparator {@link java.util.Comparator} used to perform the comparison between the + * expected value and the incoming parameter to the mocked method. + * @param operator The comparison operator, usually one of + * {@link org.easymock.LogicalOperator#LESS_THAN}, + * {@link org.easymock.LogicalOperator#LESS_OR_EQUAL}, + * {@link org.easymock.LogicalOperator#EQUAL}, {@link org.easymock.LogicalOperator#GREATER}, + * {@link org.easymock.LogicalOperator#GREATER_OR_EQUAL} + * @return {@code null}. The return value is always ignored. + */ + public static <T> T cmp(T expectedValue, Comparator<? super T> comparator, + LogicalOperator operator) { + return EasyMock.cmp(expectedValue, comparator, operator); + } + + /** + * Expect any {@code Object} as a parameter to the mocked method, but capture it for later use. + * + * {@link org.easymock.Capture} allows for capturing of the incoming value. Use + * {@link org.easymock.Capture#getValue()} to retrieve the captured value. + * + * @param <T> Type of the captured object + * @param captured a container to hold the captured value, retrieved by + * {@link org.easymock.Capture#getValue()} + * @return {@code null}. The return value is always ignored. + */ + public static <T> T capture(Capture<T> captured) { + return EasyMock.capture(captured); + } + + /** + * Expect any {@code int/Integer} as a parameter to the mocked method, but capture it for later + * use. + * + * {@link org.easymock.Capture} allows for capturing of the incoming value. Use + * {@link org.easymock.Capture#getValue()} to retrieve the captured value. + * + * @param captured a container to hold the captured value, retrieved by + * {@link org.easymock.Capture#getValue()} + * @return {@code 0}. The return value is always ignored. + */ + public static int capture(Capture<Integer> captured) { + return EasyMock.capture(captured); + } + + /** + * Expect any {@code long/Long} as a parameter to the mocked method, but capture it for later + * use. + * + * {@link org.easymock.Capture} allows for capturing of the incoming value. Use + * {@link org.easymock.Capture#getValue()} to retrieve the captured value. + * + * @param captured a container to hold the captured value, retrieved by + * {@link org.easymock.Capture#getValue()} + * @return {@code 0}. The return value is always ignored. + */ + public static long capture(Capture<Long> captured) { + return EasyMock.capture(captured); + } + + /** + * Expect any {@code float/Float} as a parameter to the mocked method, but capture it for later + * use. + * + * {@link org.easymock.Capture} allows for capturing of the incoming value. Use + * {@link org.easymock.Capture#getValue()} to retrieve the captured value. + * + * @param captured a container to hold the captured value, retrieved by + * {@link org.easymock.Capture#getValue()} + * @return {@code 0}. The return value is always ignored. + */ + public static float capture(Capture<Float> captured) { + return EasyMock.capture(captured); + } + + /** + * Expect any {@code double/Double} as a parameter to the mocked method, but capture it for later + * use. + * + * {@link org.easymock.Capture} allows for capturing of the incoming value. Use + * {@link org.easymock.Capture#getValue()} to retrieve the captured value. + * + * @param captured a container to hold the captured value, retrieved by + * {@link org.easymock.Capture#getValue()} + * @return {@code 0}. The return value is always ignored. + */ + public static double capture(Capture<Double> captured) { + return EasyMock.capture(captured); + } + + /** + * Expect any {@code byte/Byte} as a parameter to the mocked method, but capture it for later + * use. + * + * {@link org.easymock.Capture} allows for capturing of the incoming value. Use + * {@link org.easymock.Capture#getValue()} to retrieve the captured value. + * + * @param captured a container to hold the captured value, retrieved by + * {@link org.easymock.Capture#getValue()} + * @return {@code 0} + */ + public static byte capture(Capture<Byte> captured) { + return EasyMock.capture(captured); + } + + /** + * Expect any {@code char/Character} as a parameter to the mocked method, but capture it for later + * use. + * + * {@link org.easymock.Capture} allows for capturing of the incoming value. Use + * {@link org.easymock.Capture#getValue()} to retrieve the captured value. + * + * @param captured a container to hold the captured value, retrieved by + * {@link org.easymock.Capture#getValue()} + * @return {@code 0} + */ + public static char capture(Capture<Character> captured) { + return EasyMock.capture(captured); + } + + /** + * Switches the given mock objects (more exactly: the controls of the mock + * objects) to replay mode. + * + * @param mocks the mock objects. + */ + public static void replay(Object... mocks) { + for (Object mockObject : mocks) { + if (mockObject instanceof MockObject) { + EasyMock.replay(((MockObject) mockObject).getDelegate___AndroidMock()); + } else { + EasyMock.replay(mockObject); + } + } + } + + /** + * Resets the given mock objects (more exactly: the controls of the mock + * objects) allowing the mock objects to be reused. + * + * @param mocks the mock objects. + */ + public static void reset(Object... mocks) { + for (Object mockObject : mocks) { + if (mockObject instanceof MockObject) { + EasyMock.reset(((MockObject) mockObject).getDelegate___AndroidMock()); + } else { + EasyMock.reset(mockObject); + } + } + } + + /** + * Resets the given mock objects (more exactly: the controls of the mock + * objects) and change them in to mocks with nice behavior. + * {@link #createNiceMock(Class, Object...)} has more details. + * + * @param mocks the mock objects + */ + public static void resetToNice(Object... mocks) { + for (Object mockObject : mocks) { + if (mockObject instanceof MockObject) { + EasyMock.resetToNice(((MockObject) mockObject).getDelegate___AndroidMock()); + } else { + EasyMock.resetToNice(mockObject); + } + } + } + + /** + * Resets the given mock objects (more exactly: the controls of the mock + * objects) and turn them to a mock with default behavior. {@link #createMock(Class, Object...)} + * has more details. + * + * @param mocks the mock objects + */ + public static void resetToDefault(Object... mocks) { + for (Object mockObject : mocks) { + if (mockObject instanceof MockObject) { + EasyMock.resetToDefault(((MockObject) mockObject).getDelegate___AndroidMock()); + } else { + EasyMock.resetToDefault(mockObject); + } + } + } + + /** + * Resets the given mock objects (more exactly: the controls of the mock + * objects) and turn them to a mock with strict behavior. + * {@link #createStrictMock(Class, Object...)} has more details. + * + * @param mocks the mock objects + */ + public static void resetToStrict(Object... mocks) { + for (Object mockObject : mocks) { + if (mockObject instanceof MockObject) { + EasyMock.resetToStrict(((MockObject) mockObject).getDelegate___AndroidMock()); + } else { + EasyMock.resetToStrict(mockObject); + } + } + } + + /** + * Verifies that all of the expected method calls for the given mock objects (more exactly: the + * controls of the mock objects) have been executed. + * + * The {@code verify} method captures the scenario where several methods were invoked correctly, + * but some invocations did not occur. Typically, the {@code verify} method is the final thing + * invoked in a test. + * + * @param mocks the mock objects. + */ + public static void verify(Object... mocks) { + for (Object mockObject : mocks) { + if (mockObject instanceof MockObject) { + EasyMock.verify(((MockObject) mockObject).getDelegate___AndroidMock()); + } else { + EasyMock.verify(mockObject); + } + } + } + + /** + * Switches order checking of the given mock object (more exactly: the control + * of the mock object) on or off. When order checking is on, the mock will expect the method + * invokations to occur exactly in the order in which they appeared during the recording phase. + * + * @param mock the mock object. + * @param orderCheckingOn {@code true} to turn order checking on, {@code false} to turn it off. + */ + public static void checkOrder(Object mock, boolean orderCheckingOn) { + if (mock instanceof MockObject) { + EasyMock.checkOrder(((MockObject) mock).getDelegate___AndroidMock(), orderCheckingOn); + } else { + EasyMock.checkOrder(mock, orderCheckingOn); + } + } + + /** + * Reports an argument matcher. This method is needed to define custom argument + * matchers. + * + * For example: + * + * {@code + * AndroidMock.reportMatcher(new IntIsFortyTwo()); + * AndroidMock.expect(mockObject.getString(null)).andReturn("hello world");} + * + * This example will expect a parameter for {@code mockObject.getString(int)} that matches the + * conditions required by the {@code matches} method as defined by + * {@link org.easymock.IArgumentMatcher#matches(Object)}. + * + * @param matcher the matcher whose {@code matches} method will be applied to the incoming + * parameter to the mocked method. + */ + public static void reportMatcher(IArgumentMatcher matcher) { + EasyMock.reportMatcher(matcher); + } + + /** + * Returns the arguments of the current mock method call, if inside an + * {@code IAnswer} callback - be careful here, reordering parameters of a + * method changes the semantics of your tests. + * + * This method is only usable within an {@link org.easymock.IAnswer} instance. Attach an + * {@link org.easymock.IAnswer} to an expectation by using the + * {@link org.easymock.IExpectationSetters#andAnswer(org.easymock.IAnswer)} method. + * + * E.g. + * {@code AndroidMock.expect(mockObject.getString()).andAnswer(myAnswerCallback);} + * + * @return the arguments of the current mock method call. + * @throws IllegalStateException if called outside of {@code IAnswer} + * callbacks. + */ + public static Object[] getCurrentArguments() { + return EasyMock.getCurrentArguments(); + } + + /** + * Makes the mock thread safe. The mock will be usable in a multithreaded + * environment. + * + * @param mock the mock to make thread safe. + * @param threadSafe If the mock should be thread safe or not. + */ + public static void makeThreadSafe(Object mock, boolean threadSafe) { + if (mock instanceof MockObject) { + EasyMock.makeThreadSafe(((MockObject) mock).getDelegate___AndroidMock(), threadSafe); + } else { + EasyMock.makeThreadSafe(mock, threadSafe); + } + } + + @SuppressWarnings("unchecked") + private static <T, S> T getSubclassFor(Class<? super T> clazz, Class<S> delegateInterface, + Object realMock, Object... args) { + Class<T> subclass; + String className = null; + try { + if (isAndroidClass(clazz)) { + className = FileUtils.getSubclassNameFor(clazz, SdkVersion.getCurrentVersion()); + } else { + className = FileUtils.getSubclassNameFor(clazz, SdkVersion.UNKNOWN); + } + subclass = (Class<T>) Class.forName(className); + } catch (ClassNotFoundException e) { + throw new RuntimeException("Could not find class for " + className + + " which likely means that the mock-instrumented jar has not been created or else" + + " is not being used in the current runtime environment. Try running MockGeneratorMain" + + " in MockGenerator_deploy.jar or using the output of that execution as the input to" + + " the dex/apk generation.", e); + } + Constructor<T> constructor = getConstructorFor(subclass, args); + T newObject; + try { + newObject = constructor.newInstance(args); + } catch (InstantiationException e) { + throw new RuntimeException("Internal error instantiating new mock subclass" + + subclass.getName(), e); + } catch (IllegalAccessException e) { + throw new RuntimeException( + "Internal error - the new mock subclass' constructor was inaccessible", e); + } catch (InvocationTargetException e) { + throw new ExceptionInInitializerError(e); + } + Method[] methods = subclass.getMethods(); + Method setMethod; + try { + setMethod = subclass.getMethod("setDelegate___AndroidMock", delegateInterface); + } catch (NoSuchMethodException e) { + throw new RuntimeException("Internal error - No setDelegate method found for " + "class " + + subclass.getName() + " and param " + delegateInterface.getName(), e); + } + try { + setMethod.invoke(newObject, realMock); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("Internal error setting the delegate, expected " + + newObject.getClass() + " to be subclass of " + clazz.getName()); + } catch (InvocationTargetException e) { + throw new RuntimeException("Severe internal error, setDelegate threw an exception", e); + } catch (IllegalAccessException e) { + throw new RuntimeException("Internal error, setDelegate method was inaccessible", e); + } + return newObject; + } + + static boolean isUnboxableToPrimitive(Class<?> clazz, Object arg, boolean exactMatch) { + if (!clazz.isPrimitive()) { + throw new IllegalArgumentException( + "Internal Error - The class to test against is not a primitive"); + } + Class<?> unboxedType = null; + if (arg.getClass().equals(Integer.class)) { + unboxedType = Integer.TYPE; + } else if (arg.getClass().equals(Long.class)) { + unboxedType = Long.TYPE; + } else if (arg.getClass().equals(Byte.class)) { + unboxedType = Byte.TYPE; + } else if (arg.getClass().equals(Short.class)) { + unboxedType = Short.TYPE; + } else if (arg.getClass().equals(Character.class)) { + unboxedType = Character.TYPE; + } else if (arg.getClass().equals(Float.class)) { + unboxedType = Float.TYPE; + } else if (arg.getClass().equals(Double.class)) { + unboxedType = Double.TYPE; + } else if (arg.getClass().equals(Boolean.class)) { + unboxedType = Boolean.TYPE; + } else { + return false; + } + if (exactMatch) { + return clazz == unboxedType; + } + return isAssignable(clazz, unboxedType); + } + + private static boolean isAssignable(Class<?> to, Class<?> from) { + if (to == Byte.TYPE) { + return from == Byte.TYPE; + } else if (to == Short.TYPE){ + return from == Byte.TYPE || from == Short.TYPE || from == Character.TYPE; + } else if (to == Integer.TYPE || to == Character.TYPE) { + return from == Byte.TYPE || from == Short.TYPE || from == Integer.TYPE + || from == Character.TYPE; + } else if (to == Long.TYPE) { + return from == Byte.TYPE || from == Short.TYPE || from == Integer.TYPE || from == Long.TYPE + || from == Character.TYPE; + } else if (to == Float.TYPE) { + return from == Byte.TYPE || from == Short.TYPE || from == Integer.TYPE + || from == Character.TYPE || from == Float.TYPE; + } else if (to == Double.TYPE) { + return from == Byte.TYPE || from == Short.TYPE || from == Integer.TYPE || from == Long.TYPE + || from == Character.TYPE || from == Float.TYPE || from == Double.TYPE; + } else if (to == Boolean.TYPE) { + return from == Boolean.TYPE; + } else { + return to.isAssignableFrom(from); + } + } + + @SuppressWarnings("unchecked") + static <T> Constructor<T> getConstructorFor(Class<T> clazz, Object... args) + throws SecurityException { + Constructor<T>[] constructors = (Constructor<T>[]) clazz.getConstructors(); + Constructor<T> compatibleConstructor = null; + for (Constructor<T> constructor : constructors) { + Class<?>[] params = constructor.getParameterTypes(); + if (params.length == args.length) { + boolean exactMatch = true; + boolean compatibleMatch = true; + for (int i = 0; i < params.length; ++i) { + Object arg = args[i]; + if (arg == null) { + arg = Void.TYPE; + } + if (!params[i].isAssignableFrom(arg.getClass())) { + if (params[i].isPrimitive()) { + exactMatch &= isUnboxableToPrimitive(params[i], arg, true); + compatibleMatch &= isUnboxableToPrimitive(params[i], arg, false); + } else { + exactMatch = false; + compatibleMatch = false; + } + } + } + if (exactMatch) { + return constructor; + } else if (compatibleMatch) { + compatibleConstructor = constructor; + } + } + } + if (compatibleConstructor != null) { + return compatibleConstructor; + } + List<String> argTypes = new ArrayList<String>(args.length); + for (Object arg : args) { + argTypes.add(arg == null ? "<null>" : arg.getClass().toString()); + } + throw new IllegalArgumentException("Could not find the specified Constructor: " + + clazz.getName() + "(" + argTypes + ")"); + } + + @SuppressWarnings("unchecked") + private static <T> Class<T> getInterfaceFor(Class<T> clazz) { + try { + String className; + if (isAndroidClass(clazz)) { + className = FileUtils.getInterfaceNameFor(clazz, SdkVersion.getCurrentVersion()); + } else { + className = FileUtils.getInterfaceNameFor(clazz, SdkVersion.UNKNOWN); + } + return (Class<T>) Class.forName(className); + } catch (ClassNotFoundException e) { + throw new RuntimeException("Could not find mock for " + clazz.getName() + + " -- Make sure to run the MockGenerator.jar on your test jar, and to " + + "build the Android test APK using the modified jar created by MockGenerator", e); + } + } + + static boolean isAndroidClass(Class<?> clazz) { + String packageName = clazz.getPackage().getName(); + return packageName.startsWith("android.") || packageName.startsWith("dalvik.") + || packageName.startsWith("java.") || packageName.startsWith("javax.") + || packageName.startsWith("org.xml.sax") || packageName.startsWith("org.xmlpull.v1") + || packageName.startsWith("org.w3c.dom") || packageName.startsWith("org.apache.http") + || packageName.startsWith("junit."); + } +} diff --git a/src/com/google/android/testing/mocking/AndroidMockGenerator.java b/src/com/google/android/testing/mocking/AndroidMockGenerator.java new file mode 100644 index 0000000..1a557a6 --- /dev/null +++ b/src/com/google/android/testing/mocking/AndroidMockGenerator.java @@ -0,0 +1,480 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.testing.mocking; + +import javassist.CannotCompileException; +import javassist.ClassClassPath; +import javassist.ClassPool; +import javassist.CtClass; +import javassist.CtConstructor; +import javassist.CtField; +import javassist.CtMethod; +import javassist.CtNewConstructor; +import javassist.NotFoundException; + +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + + +/** + * AndroidMockGenerator creates the subclass and interface required for mocking + * a given Class. + * + * The only public method of AndroidMockGenerator is createMocksForClass. See + * the javadocs for this method for more information about AndroidMockGenerator. + * + * @author swoodward@google.com (Stephen Woodward) + */ +class AndroidMockGenerator { + public AndroidMockGenerator() { + ClassPool.doPruning = false; + ClassPool.getDefault().insertClassPath(new ClassClassPath(MockObject.class)); + } + + /** + * Creates a List of javassist.CtClass objects representing all of the + * interfaces and subclasses required to meet the Mocking requests of the + * Class specified by {@code clazz}. + * + * A test class can request that a Class be prepared for mocking by using the + * {@link UsesMocks} annotation at either the Class or Method level. All + * classes specified by these annotations will have exactly two CtClass + * objects created, one for a generated interface, and one for a generated + * subclass. The interface and subclass both define the same methods which + * comprise all of the mockable methods of the provided class. At present, for + * a method to be mockable, it must be non-final and non-static, although this + * may expand in the future. + * + * The class itself must be mockable, otherwise this method will ignore the + * requested mock and print a warning. At present, a class is mockable if it + * is a non-final publicly-instantiable Java class that is assignable from the + * java.lang.Object class. See the javadocs for + * {@link java.lang.Class#isAssignableFrom(Class)} for more information about + * what "is assignable from the Object class" means. As a non-exhaustive + * example, if a given Class represents an Enum, Annotation, Primitive or + * Array, then it is not assignable from Object. Interfaces are also ignored + * since these need no modifications in order to be mocked. + * + * @param clazz the Class object to have all of its UsesMocks annotations + * processed and the corresponding Mock Classes created. + * @return a List of CtClass objects representing the Classes and Interfaces + * required for mocking the classes requested by {@code clazz} + * @throws ClassNotFoundException + * @throws CannotCompileException + * @throws IOException + */ + public List<GeneratedClassFile> createMocksForClass(Class<?> clazz) + throws ClassNotFoundException, IOException, CannotCompileException { + return this.createMocksForClass(clazz, SdkVersion.UNKNOWN); + } + + public List<GeneratedClassFile> createMocksForClass(Class<?> clazz, SdkVersion sdkVersion) + throws ClassNotFoundException, IOException, CannotCompileException { + if (!classIsSupportedType(clazz)) { + reportReasonForUnsupportedType(clazz); + return Arrays.asList(new GeneratedClassFile[0]); + } + CtClass newInterfaceCtClass = generateInterface(clazz, sdkVersion); + GeneratedClassFile newInterface = new GeneratedClassFile(newInterfaceCtClass.getName(), + newInterfaceCtClass.toBytecode()); + CtClass mockDelegateCtClass = generateSubClass(clazz, newInterfaceCtClass, sdkVersion); + GeneratedClassFile mockDelegate = new GeneratedClassFile(mockDelegateCtClass.getName(), + mockDelegateCtClass.toBytecode()); + return Arrays.asList(new GeneratedClassFile[] {newInterface, mockDelegate}); + } + + private void reportReasonForUnsupportedType(Class<?> clazz) { + String reason = null; + if (clazz.isInterface()) { + // do nothing to make sure none of the other conditions apply. + } else if (clazz.isEnum()) { + reason = "Cannot mock an Enum"; + } else if (clazz.isAnnotation()) { + reason = "Cannot mock an Annotation"; + } else if (clazz.isArray()) { + reason = "Cannot mock an Array"; + } else if (Modifier.isFinal(clazz.getModifiers())) { + reason = "Cannot mock a Final class"; + } else if (clazz.isPrimitive()) { + reason = "Cannot mock primitives"; + } else if (!Object.class.isAssignableFrom(clazz)) { + reason = "Cannot mock non-classes"; + } else if (!containsUsableConstructor(clazz)) { + reason = "Cannot mock a class with no public constructors"; + } else { + // Whatever the reason is, it's not one that we care about. + } + if (reason != null) { + // Sometimes we want to be silent, so check 'reason' against null. + System.err.println(reason + ": " + clazz.getName()); + } + } + + private boolean containsUsableConstructor(Class<?> clazz) { + Constructor<?>[] constructors = clazz.getDeclaredConstructors(); + for (Constructor<?> constructor : constructors) { + if (Modifier.isPublic(constructor.getModifiers()) || + Modifier.isProtected(constructor.getModifiers())) { + return true; + } + } + return false; + } + + boolean classIsSupportedType(Class<?> clazz) { + return (containsUsableConstructor(clazz)) && Object.class.isAssignableFrom(clazz) + && !clazz.isInterface() && !clazz.isEnum() && !clazz.isAnnotation() && !clazz.isArray() + && !Modifier.isFinal(clazz.getModifiers()); + } + + void saveCtClass(CtClass clazz) throws ClassNotFoundException, IOException { + try { + clazz.writeFile(); + } catch (NotFoundException e) { + throw new ClassNotFoundException("Error while saving modified class " + clazz.getName(), e); + } catch (CannotCompileException e) { + throw new RuntimeException("Internal Error: Attempt to save syntactically incorrect code " + + "for class " + clazz.getName(), e); + } + } + + CtClass generateInterface(Class<?> originalClass, SdkVersion sdkVersion) { + ClassPool classPool = getClassPool(); + try { + return classPool.getCtClass(FileUtils.getInterfaceNameFor(originalClass, sdkVersion)); + } catch (NotFoundException e) { + CtClass newInterface = + classPool.makeInterface(FileUtils.getInterfaceNameFor(originalClass, sdkVersion)); + addInterfaceMethods(originalClass, newInterface); + return newInterface; + } + } + + String getInterfaceMethodSource(Method method) throws UnsupportedOperationException { + StringBuilder methodBody = getMethodSignature(method); + methodBody.append(";"); + return methodBody.toString(); + } + + private StringBuilder getMethodSignature(Method method) { + int modifiers = method.getModifiers(); + if (Modifier.isFinal(modifiers) || Modifier.isStatic(modifiers)) { + throw new UnsupportedOperationException( + "Cannot specify final or static methods in an interface"); + } + StringBuilder methodSignature = new StringBuilder("public "); + methodSignature.append(getClassName(method.getReturnType())); + methodSignature.append(" "); + methodSignature.append(method.getName()); + methodSignature.append("("); + int i = 0; + for (Class<?> arg : method.getParameterTypes()) { + methodSignature.append(getClassName(arg)); + methodSignature.append(" arg"); + methodSignature.append(i); + if (i < method.getParameterTypes().length - 1) { + methodSignature.append(","); + } + i++; + } + methodSignature.append(")"); + if (method.getExceptionTypes().length > 0) { + methodSignature.append(" throws "); + } + i = 0; + for (Class<?> exception : method.getExceptionTypes()) { + methodSignature.append(getClassName(exception)); + if (i < method.getExceptionTypes().length - 1) { + methodSignature.append(","); + } + i++; + } + return methodSignature; + } + + private String getClassName(Class<?> clazz) { + return clazz.getCanonicalName(); + } + + static ClassPool getClassPool() { + return ClassPool.getDefault(); + } + + private boolean classExists(String name) { + // The following line is the ideal, but doesn't work (bug in library). + // return getClassPool().find(name) != null; + try { + getClassPool().get(name); + return true; + } catch (NotFoundException e) { + return false; + } + } + + CtClass generateSubClass(Class<?> superClass, CtClass newInterface, SdkVersion sdkVersion) + throws ClassNotFoundException { + if (classExists(FileUtils.getSubclassNameFor(superClass, sdkVersion))) { + try { + return getClassPool().get(FileUtils.getSubclassNameFor(superClass, sdkVersion)); + } catch (NotFoundException e) { + throw new ClassNotFoundException("This should be impossible, since we just checked for " + + "the existence of the class being created", e); + } + } + CtClass newClass = generateSkeletalClass(superClass, newInterface, sdkVersion); + if (!newClass.isFrozen()) { + newClass.addInterface(newInterface); + try { + newClass.addInterface(getClassPool().get(MockObject.class.getName())); + } catch (NotFoundException e) { + throw new ClassNotFoundException("Could not find " + MockObject.class.getName(), e); + } + addMethods(superClass, newClass); + addGetDelegateMethod(newClass); + addSetDelegateMethod(newClass, newInterface); + addConstructors(newClass, superClass); + } + return newClass; + } + + private void addConstructors(CtClass clazz, Class<?> superClass) throws ClassNotFoundException { + CtClass superCtClass = getCtClassForClass(superClass); + + CtConstructor[] constructors = superCtClass.getDeclaredConstructors(); + for (CtConstructor constructor : constructors) { + int modifiers = constructor.getModifiers(); + if (Modifier.isPublic(modifiers) || Modifier.isProtected(modifiers)) { + CtConstructor ctConstructor; + try { + ctConstructor = CtNewConstructor.make(constructor.getParameterTypes(), + constructor.getExceptionTypes(), clazz); + clazz.addConstructor(ctConstructor); + } catch (CannotCompileException e) { + throw new RuntimeException("Internal Error - Could not add constructors.", e); + } catch (NotFoundException e) { + throw new RuntimeException("Internal Error - Constructor suddenly could not be found", e); + } + } + } + } + + CtClass getCtClassForClass(Class<?> clazz) throws ClassNotFoundException { + ClassPool classPool = getClassPool(); + try { + return classPool.get(clazz.getName()); + } catch (NotFoundException e) { + throw new ClassNotFoundException("Class not found when finding the class to be mocked: " + + clazz.getName(), e); + } + } + + private void addSetDelegateMethod(CtClass clazz, CtClass newInterface) { + try { + clazz.addMethod(CtMethod.make(getSetDelegateMethodSource(newInterface), clazz)); + } catch (CannotCompileException e) { + throw new RuntimeException("Internal error while creating the setDelegate() method", e); + } + } + + String getSetDelegateMethodSource(CtClass newInterface) { + return "public void setDelegate___AndroidMock(" + newInterface.getName() + " obj) { this." + + getDelegateFieldName() + " = obj;}"; + } + + private void addGetDelegateMethod(CtClass clazz) { + try { + CtMethod newMethod = CtMethod.make(getGetDelegateMethodSource(), clazz); + try { + CtMethod existingMethod = clazz.getMethod(newMethod.getName(), newMethod.getSignature()); + clazz.removeMethod(existingMethod); + } catch (NotFoundException e) { + // expected path... sigh. + } + clazz.addMethod(newMethod); + } catch (CannotCompileException e) { + throw new RuntimeException("Internal error while creating the getDelegate() method", e); + } + } + + private String getGetDelegateMethodSource() { + return "public Object getDelegate___AndroidMock() { return this." + getDelegateFieldName() + + "; }"; + } + + String getDelegateFieldName() { + return "delegateMockObject"; + } + + void addInterfaceMethods(Class<?> originalClass, CtClass newInterface) { + Method[] methods = getAllMethods(originalClass); + for (Method method : methods) { + try { + if (isMockable(method)) { + CtMethod newMethod = CtMethod.make(getInterfaceMethodSource(method), newInterface); + newInterface.addMethod(newMethod); + } + } catch (UnsupportedOperationException e) { + // Can't handle finals and statics. + } catch (CannotCompileException e) { + throw new RuntimeException( + "Internal error while creating a new Interface method for class " + + originalClass.getName() + ". Method name: " + method.getName(), e); + } + } + } + + void addMethods(Class<?> superClass, CtClass newClass) { + Method[] methods = getAllMethods(superClass); + if (newClass.isFrozen()) { + newClass.defrost(); + } + List<CtMethod> existingMethods = Arrays.asList(newClass.getDeclaredMethods()); + for (Method method : methods) { + try { + if (isMockable(method)) { + CtMethod newMethod = CtMethod.make(getDelegateMethodSource(method), newClass); + if (!existingMethods.contains(newMethod)) { + newClass.addMethod(newMethod); + } + } + } catch (UnsupportedOperationException e) { + // Can't handle finals and statics. + } catch (CannotCompileException e) { + throw new RuntimeException("Internal Error while creating subclass methods for " + + newClass.getName() + " method: " + method.getName(), e); + } + } + } + + Method[] getAllMethods(Class<?> clazz) { + Map<String, Method> methodMap = getAllMethodsMap(clazz); + return methodMap.values().toArray(new Method[0]); + } + + private Map<String, Method> getAllMethodsMap(Class<?> clazz) { + Map<String, Method> methodMap = new HashMap<String, Method>(); + Class<?> superClass = clazz.getSuperclass(); + if (superClass != null) { + methodMap.putAll(getAllMethodsMap(superClass)); + } + List<Method> methods = new ArrayList<Method>(Arrays.asList(clazz.getDeclaredMethods())); + for (Method method : methods) { + String key = method.getName(); + for (Class<?> param : method.getParameterTypes()) { + key += param.getCanonicalName(); + } + methodMap.put(key, method); + } + return methodMap; + } + + boolean isMockable(Method method) { + if (isForbiddenMethod(method)) { + return false; + } + int modifiers = method.getModifiers(); + return !Modifier.isFinal(modifiers) && !Modifier.isStatic(modifiers) && !method.isBridge() + && (Modifier.isPublic(modifiers) || Modifier.isProtected(modifiers)); + } + + boolean isForbiddenMethod(Method method) { + if (method.getName().equals("equals")) { + return method.getParameterTypes().length == 1 + && method.getParameterTypes()[0].equals(Object.class); + } else if (method.getName().equals("toString")) { + return method.getParameterTypes().length == 0; + } else if (method.getName().equals("hashCode")) { + return method.getParameterTypes().length == 0; + } + return false; + } + + private String getReturnDefault(Method method) { + Class<?> returnType = method.getReturnType(); + if (!returnType.isPrimitive()) { + return "null"; + } else if (returnType == Boolean.TYPE) { + return "false"; + } else if (returnType == Void.TYPE) { + return ""; + } else { + return "(" + returnType.getName() + ")0"; + } + } + + String getDelegateMethodSource(Method method) { + StringBuilder methodBody = getMethodSignature(method); + methodBody.append("{"); + methodBody.append("if(this."); + methodBody.append(getDelegateFieldName()); + methodBody.append("==null){return "); + methodBody.append(getReturnDefault(method)); + methodBody.append(";}"); + if (!method.getReturnType().equals(Void.TYPE)) { + methodBody.append("return "); + } + methodBody.append("this."); + methodBody.append(getDelegateFieldName()); + methodBody.append("."); + methodBody.append(method.getName()); + methodBody.append("("); + for (int i = 0; i < method.getParameterTypes().length; ++i) { + methodBody.append("arg"); + methodBody.append(i); + if (i < method.getParameterTypes().length - 1) { + methodBody.append(","); + } + } + methodBody.append(");}"); + return methodBody.toString(); + } + + CtClass generateSkeletalClass(Class<?> superClass, CtClass newInterface, SdkVersion sdkVersion) + throws ClassNotFoundException { + ClassPool classPool = getClassPool(); + CtClass superCtClass = getCtClassForClass(superClass); + String subclassName = FileUtils.getSubclassNameFor(superClass, sdkVersion); + + CtClass newClass; + try { + newClass = classPool.makeClass(subclassName, superCtClass); + } catch (RuntimeException e) { + if (e.getMessage().contains("frozen class")) { + try { + return classPool.get(subclassName); + } catch (NotFoundException ex) { + throw new ClassNotFoundException("Internal Error: could not find class", ex); + } + } + throw e; + } + + try { + newClass.addField(new CtField(newInterface, getDelegateFieldName(), newClass)); + } catch (CannotCompileException e) { + throw new RuntimeException("Internal error adding the delegate field to " + + newClass.getName(), e); + } + return newClass; + } +} diff --git a/src/com/google/android/testing/mocking/FileUtils.java b/src/com/google/android/testing/mocking/FileUtils.java new file mode 100644 index 0000000..f759c57 --- /dev/null +++ b/src/com/google/android/testing/mocking/FileUtils.java @@ -0,0 +1,77 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.testing.mocking; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; + +/** + * @author swoodward@google.com (Stephen Woodward) + */ +public class FileUtils { + + /** + * @param clazz + * @param sdkVersion + * @return the appropriate interface name for the interface mock support file. + */ + static String getInterfaceNameFor(Class<?> clazz, SdkVersion sdkVersion) { + return sdkVersion.getPackagePrefix() + "genmocks." + clazz.getName() + "DelegateInterface"; + } + /** + * @param clazz + * @param sdkVersion + * @return the appropriate subclass name for the subclass mock support file. + */ + static String getSubclassNameFor(Class<?> clazz, SdkVersion sdkVersion) { + return sdkVersion.getPackagePrefix() + "genmocks." + clazz.getName() + "DelegateSubclass"; + } + + /** + * Converts a class name into the a .class filename. + * + * @param className + * @return the file name for the specified class name. + */ + static String getFilenameFor(String className) { + return className.replace('.', File.separatorChar) + ".class"; + } + + /** + * Converts a filename into a class name. + * + * @param filename + * @return the class name for the specified file name. + */ + static String getClassNameFor(String filename) { + if (!filename.endsWith(".class")) { + throw new IllegalArgumentException("Argument provided is not a class filename: " + filename); + } + return filename.replace(File.separatorChar, '.').substring(0, filename.length() - 6); + } + + static void saveClassToFolder(GeneratedClassFile clazz, String outputFolderName) + throws FileNotFoundException, IOException { + File classFolder = new File(outputFolderName); + File targetFile = new File(classFolder, getFilenameFor(clazz.getClassName())); + targetFile.getParentFile().mkdirs(); + FileOutputStream outputStream = new FileOutputStream(targetFile); + outputStream.write(clazz.getContents()); + outputStream.close(); + } +} diff --git a/src/com/google/android/testing/mocking/GeneratedClassFile.java b/src/com/google/android/testing/mocking/GeneratedClassFile.java new file mode 100644 index 0000000..1ef01ed --- /dev/null +++ b/src/com/google/android/testing/mocking/GeneratedClassFile.java @@ -0,0 +1,54 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.testing.mocking; + +/** + * Represents the contents of a Class file. + * + * @author swoodward@google.com (Stephen Woodward) + */ +public class GeneratedClassFile { + private final String className; + private final byte[] contents; + + /** + * @param name the fully qualified name of the class. + * @param classFileContents the binary contents of the file. + */ + public GeneratedClassFile(String name, byte[] classFileContents) { + className = name; + contents = classFileContents; + } + + public String getClassName() { + return className; + } + + public byte[] getContents() { + return contents; + } + + @Override + public int hashCode() { + return (this.getClass().getName() + className).hashCode(); + } + + @Override + public boolean equals(Object obj) { + return (obj instanceof GeneratedClassFile) + && className.equals(((GeneratedClassFile) obj).getClassName()); + } +} diff --git a/src/com/google/android/testing/mocking/GeneratedMockJar.readme b/src/com/google/android/testing/mocking/GeneratedMockJar.readme new file mode 100644 index 0000000..858ec6f --- /dev/null +++ b/src/com/google/android/testing/mocking/GeneratedMockJar.readme @@ -0,0 +1,32 @@ +Android Mock + +Copyright 2010 Google Inc. +All Rights Reserved. +Author: swoodward@google.com (Stephen Woodward) + + +Android Mock is a wrapper for EasyMock (2.4) which allows for real Class mocking on +an Android (Dalvik) VM. + +All methods on Android Mock are syntactically equivalent to EasyMock method +calls, and will delegate calls to EasyMock, while performing the required +transformations to avoid Dalvik VM troubles. + +Calls directly to EasyMock will work correctly only if the Class being mocked +is in fact an Interface. Calls to Android Mock will work correctly for both +Interfaces and concrete Classes. + +Android Mock requires that the code being mocked be instrumented prior to +loading to the Dalvik VM by having called the MockGenerator.jar file. Try +running java -jar MockGenerator.jar --help for more information. + +An example usage pattern is: + +@UsesMocks(MyClass.class) +public void testFoo() MyClass { + mockObject = AndroidMock.createMock(MyClass.class); + AndroidMock.expect(mockObject.foo(0)).andReturn(42); + AndroidMock.replay(mockObject); assertEquals(42, mockObject.foo(0)); + AndroidMock.verify(mockObject); +} + diff --git a/src/com/google/android/testing/mocking/MockObject.java b/src/com/google/android/testing/mocking/MockObject.java new file mode 100644 index 0000000..513c900 --- /dev/null +++ b/src/com/google/android/testing/mocking/MockObject.java @@ -0,0 +1,32 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.testing.mocking; + +/** + * Defines the getDelegate___AndroidMock method used by Android Mock for + * delegating Android Mock calls to the EasyMock generated MockObject. + * + * @author swoodward@google.com (Stephen Woodward) + */ +public interface MockObject { + /** + * Accessor method to get the wrapped EasyMock mock object. + * + * @return a mock object created by EasyMock and wrapped by the object + * implementing this method. + */ + Object getDelegate___AndroidMock(); +} diff --git a/src/com/google/android/testing/mocking/ProcessorLogger.java b/src/com/google/android/testing/mocking/ProcessorLogger.java new file mode 100644 index 0000000..d2d853c --- /dev/null +++ b/src/com/google/android/testing/mocking/ProcessorLogger.java @@ -0,0 +1,106 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.testing.mocking; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.net.URL; +import java.net.URLClassLoader; +import java.text.SimpleDateFormat; +import java.util.Date; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.tools.Diagnostic.Kind; + +/** + * @author swoodward@google.com (Stephen Woodward) + * + */ +class ProcessorLogger { + private final OutputStream logFile; + private final ProcessingEnvironment processingEnv; + + ProcessorLogger(OutputStream logFile, ProcessingEnvironment processingEnv) { + this.logFile = logFile; + this.processingEnv = processingEnv; + } + + ProcessorLogger(String logFileName, ProcessingEnvironment processingEnv) { + this.logFile = openLogFile(logFileName); + this.processingEnv = processingEnv; + } + + void reportClasspathError(String clazz, Throwable e) { + printMessage(Kind.ERROR, "Could not find " + clazz); + printMessage(Kind.ERROR, e); + printMessage(Kind.NOTE, "Known Classpath: "); + URL[] allUrls = ((URLClassLoader) this.getClass().getClassLoader()).getURLs(); + for (URL url : allUrls) { + printMessage(Kind.NOTE, url.toString()); + } + } + + void printMessage(Kind kind, String message) { + processingEnv.getMessager().printMessage(kind, message); + if (logFile != null) { + try { + logFile.write((SimpleDateFormat.getDateTimeInstance().format(new Date()) + " - " + + kind.toString() + " : " + message + "\n").getBytes()); + } catch (IOException e) { + // That's unfortunate, but not much to do about it. + processingEnv.getMessager().printMessage(Kind.WARNING, + "IOException logging to file" + e.toString()); + } + } + } + + void printMessage(Kind kind, Throwable e) { + ByteArrayOutputStream stackTraceByteStream = new ByteArrayOutputStream(); + PrintStream stackTraceStream = new PrintStream(stackTraceByteStream); + e.printStackTrace(stackTraceStream); + printMessage(kind, stackTraceByteStream.toString()); + } + + FileOutputStream openLogFile(String logFileName) { + try { + if (logFileName != null) { + File log = new File(logFileName); + if (!log.exists() && log.getParentFile() != null) { + log.getParentFile().mkdirs(); + } + return new FileOutputStream(log, true); + } + } catch (FileNotFoundException e) { + printMessage(Kind.WARNING, e); + } + return null; + } + + void close() { + if (logFile != null) { + try { + logFile.close(); + } catch (IOException e) { + // That's ok + } + } + } +} diff --git a/src/com/google/android/testing/mocking/SdkVersion.java b/src/com/google/android/testing/mocking/SdkVersion.java new file mode 100644 index 0000000..ee61707 --- /dev/null +++ b/src/com/google/android/testing/mocking/SdkVersion.java @@ -0,0 +1,106 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.testing.mocking; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; + + +/** + * Represents different SDK versions of the Android SDK. + * + * @author swoodward@google.com (Stephen Woodward) + */ +public enum SdkVersion { + UNKNOWN("", -1), CUPCAKE("v15", 3), DONUT("v16", 4), ECLAIR_0_1("v201", 6), + ECLAIR_MR1("v21", 7), FROYO("v22", 8); + + private static final int SDK_VERSION; + + static { + String sdkString = null; + int sdkInt; + try { + Class<?> buildClass = Class.forName("android.os.Build$VERSION"); + Field sdkField = buildClass.getField("SDK"); + sdkString = (String) sdkField.get(null); + sdkInt = Integer.parseInt(sdkString); + } catch (Exception e) { + // This will always happen on the desktop side. No big deal. + if (sdkString != null) { + // But this is unexpected + System.out.println(e.toString()); + e.printStackTrace(); + } + sdkInt = -1; + } + SDK_VERSION = sdkInt; + } + + private final String prefix; + private final String versionName; + private final int apiLevel; + + private SdkVersion(String packagePrefix, int apiLevel) { + versionName = packagePrefix; + prefix = packagePrefix.length() == 0 ? "" : packagePrefix + "."; + this.apiLevel = apiLevel; + } + + /** + * Returns an array of SdkVersion objects. This is to be favoured over the + * {@link #values()} method, since that method will also return the UNKNOWN + * SDK version, which is not usually a valid version on which to operate. + * + * @return an array of SdkVersion objects. + */ + public static SdkVersion[] getAllVersions() { + List<SdkVersion> versions = new ArrayList<SdkVersion>(); + for (SdkVersion version : values()) { + if (!version.equals(UNKNOWN)) { + versions.add(version); + } + } + return versions.toArray(new SdkVersion[versions.size()]); + } + + public String getVersionName() { + return versionName; + } + + public String getPackagePrefix() { + return prefix; + } + + /** + * Returns the current SDK version, or UNKNOWN if the version cannot be determined (for instance + * if this method is invoked from within a J2SE environment). + * @return the current SDK version. + */ + public static SdkVersion getCurrentVersion() { + return getVersionFor(SDK_VERSION); + } + + static SdkVersion getVersionFor(int apiLevel) { + for (SdkVersion version : values()) { + if (version.apiLevel == apiLevel) { + return version; + } + } + return UNKNOWN; + } +} diff --git a/src/com/google/android/testing/mocking/UsesMocks.java b/src/com/google/android/testing/mocking/UsesMocks.java new file mode 100644 index 0000000..55cd1de --- /dev/null +++ b/src/com/google/android/testing/mocking/UsesMocks.java @@ -0,0 +1,33 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.testing.mocking; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Annotation that indicates that a Class should be prepared for Mocking. + * + * E.g. {@code @UsesMocks(ClassToMock.class)} will indicate that ClassToMock should be prepared + * for mocking. Preparation for mocking involves the creation of new classes that will then be + * available at runtime on the Dalvik VM (assuming that the Jar file generated by MockGenerator is + * added to the APK uploaded to the device/emulator). + * @author swoodward@google.com (Stephen Woodward) + */ +@Retention(RetentionPolicy.RUNTIME) +public @interface UsesMocks { + Class<?>[] value(); +} diff --git a/src/com/google/android/testing/mocking/UsesMocksProcessor.java b/src/com/google/android/testing/mocking/UsesMocksProcessor.java new file mode 100644 index 0000000..0ca5c48 --- /dev/null +++ b/src/com/google/android/testing/mocking/UsesMocksProcessor.java @@ -0,0 +1,240 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.testing.mocking; + +import javassist.CannotCompileException; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.annotation.processing.SupportedOptions; +import javax.annotation.processing.SupportedSourceVersion; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; +import javax.tools.Diagnostic.Kind; +import javax.tools.JavaFileObject; + + +/** + * Annotation Processor to generate the mocks for Android Mock. + * + * This processor will automatically create mocks for all classes + * specified by {@link UsesMocks} annotations. + * + * @author swoodward@google.com (Stephen Woodward) + */ +@SupportedAnnotationTypes("com.google.android.testing.mocking.UsesMocks") +@SupportedSourceVersion(SourceVersion.RELEASE_5) +@SupportedOptions({ + UsesMocksProcessor.REGENERATE_FRAMEWORK_MOCKS, + UsesMocksProcessor.LOGFILE, + UsesMocksProcessor.BIN_DIR +}) +public class UsesMocksProcessor extends AbstractProcessor { + public static final String LOGFILE = "logfile"; + public static final String REGENERATE_FRAMEWORK_MOCKS = "RegenerateFrameworkMocks"; + public static final String BIN_DIR = "bin_dir"; + private AndroidMockGenerator mockGenerator = new AndroidMockGenerator(); + private AndroidFrameworkMockGenerator frameworkMockGenerator = + new AndroidFrameworkMockGenerator(); + ProcessorLogger logger; + + /** + * Main entry point of the processor. This is called by the Annotation framework. + * {@link javax.annotation.processing.AbstractProcessor} for more details. + */ + @Override + public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment environment) { + try { + prepareLogger(); + List<Class<?>> classesToMock = getClassesToMock(environment); + Set<GeneratedClassFile> mockedClassesSet = getMocksFor(classesToMock); + writeMocks(mockedClassesSet); + } catch (Exception e) { + logger.printMessage(Kind.ERROR, e); + } finally { + logger.close(); + } + return false; + } + + /** + * Returns a Set of GeneratedClassFile objects which represent all of the classes to be mocked. + * + * @param classesToMock the list of classes which need to be mocked. + * @return a set of mock support classes to support the mocking of all the classes specified in + * {@literal classesToMock}. + */ + private Set<GeneratedClassFile> getMocksFor(List<Class<?>> classesToMock) throws IOException, + CannotCompileException { + logger.printMessage(Kind.NOTE, "Found " + classesToMock.size() + " classes to mock"); + boolean regenerateFrameworkMocks = processingEnv.getOptions().get( + REGENERATE_FRAMEWORK_MOCKS) != null; + if (regenerateFrameworkMocks) { + logger.printMessage(Kind.NOTE, "Regenerating Framework Mocks on Request"); + } + Set<GeneratedClassFile> mockedClassesSet = + getClassMocks(classesToMock, regenerateFrameworkMocks); + logger.printMessage(Kind.NOTE, "Found " + mockedClassesSet.size() + + " mocked classes to save"); + return mockedClassesSet; + } + + /** + * @param environment the environment for this round of processing as provided to the main + * {@link #process(Set, RoundEnvironment)} method. + * @return a List of Class objects for the classes that need to be mocked. + */ + private List<Class<?>> getClassesToMock(RoundEnvironment environment) { + logger.printMessage(Kind.NOTE, "Start Processing Annotations"); + List<Class<?>> classesToMock = new ArrayList<Class<?>>(); + classesToMock.addAll( + findClassesToMock(environment.getElementsAnnotatedWith(UsesMocks.class))); + return classesToMock; + } + + private void prepareLogger() { + if (logger == null) { + logger = new ProcessorLogger(processingEnv.getOptions().get(LOGFILE), processingEnv); + } + } + + /** + * Finds all of the classes that should be mocked, based on {@link UsesMocks} annotations + * in the various source files being compiled. + * + * @param annotatedElements a Set of all elements holding {@link UsesMocks} annotations. + * @return all of the classes that should be mocked. + */ + List<Class<?>> findClassesToMock(Set<? extends Element> annotatedElements) { + logger.printMessage(Kind.NOTE, "Processing " + annotatedElements); + List<Class<?>> classList = new ArrayList<Class<?>>(); + for (Element annotation : annotatedElements) { + List<? extends AnnotationMirror> mirrors = annotation.getAnnotationMirrors(); + for (AnnotationMirror mirror : mirrors) { + if (mirror.getAnnotationType().toString().equals(UsesMocks.class.getName())) { + for (AnnotationValue annotationValue : mirror.getElementValues().values()) { + for (Object classFileName : (Iterable<?>) annotationValue.getValue()) { + String className = classFileName.toString(); + if (className.endsWith(".class")) { + className = className.substring(0, className.length() - 6); + } + logger.printMessage(Kind.NOTE, "Adding Class to Mocking List: " + className); + try { + classList.add(Class.forName(className, false, getClass().getClassLoader())); + } catch (ClassNotFoundException e) { + logger.reportClasspathError(className, e); + } + } + } + } + } + } + return classList; + } + + /** + * Gets a set of GeneratedClassFiles to represent all of the support classes required to + * mock the List of classes provided in {@code classesToMock}. + * @param classesToMock the list of classes to be mocked. + * @param regenerateFrameworkMocks if true, then mocks for the framework classes will be created + * instead of pulled from the existing set of framework support classes. + * @return a Set of {@link GeneratedClassFile} for all of the mocked classes. + */ + Set<GeneratedClassFile> getClassMocks(List<Class<?>> classesToMock, + boolean regenerateFrameworkMocks) throws IOException, CannotCompileException { + Set<GeneratedClassFile> mockedClassesSet = new HashSet<GeneratedClassFile>(); + for (Class<?> clazz : classesToMock) { + try { + logger.printMessage(Kind.NOTE, "Mocking " + clazz); + if (!AndroidMock.isAndroidClass(clazz) || regenerateFrameworkMocks) { + mockedClassesSet.addAll(getAndroidMockGenerator().createMocksForClass(clazz)); + } else { + mockedClassesSet.addAll(getAndroidFrameworkMockGenerator().getMocksForClass(clazz)); + } + } catch (ClassNotFoundException e) { + logger.reportClasspathError(clazz.getName(), e); + } catch (NoClassDefFoundError e) { + logger.reportClasspathError(clazz.getName(), e); + } + } + return mockedClassesSet; + } + + private AndroidFrameworkMockGenerator getAndroidFrameworkMockGenerator() { + return frameworkMockGenerator; + } + + /** + * Writes the provided mocks from {@code mockedClassesSet} to the bin folder alongside the + * .class files being generated by the javac call which invoked this annotation processor. + * In Eclipse, additional information is needed as the Eclipse annotation processor framework + * is missing key functionality required by this method. Instead the classes are saved using + * a FileOutputStream and the -Abin_dir processor option must be set. + * @param mockedClassesSet the set of mocks to be saved. + */ + void writeMocks(Set<GeneratedClassFile> mockedClassesSet) { + for (GeneratedClassFile clazz : mockedClassesSet) { + OutputStream classFileStream; + try { + logger.printMessage(Kind.NOTE, "Saving " + clazz.getClassName()); + JavaFileObject classFile = processingEnv.getFiler().createClassFile(clazz.getClassName()); + classFileStream = classFile.openOutputStream(); + classFileStream.write(clazz.getContents()); + classFileStream.close(); + } catch (IOException e) { + logger.printMessage(Kind.ERROR, "Internal Error saving mock: " + clazz.getClassName()); + logger.printMessage(Kind.ERROR, e); + } catch (UnsupportedOperationException e) { + // Eclipse annotation processing doesn't support class creation. + logger.printMessage(Kind.NOTE, "Saving via Eclipse " + clazz.getClassName()); + saveMocksEclipse(clazz, processingEnv.getOptions().get(BIN_DIR).toString().trim()); + } + } + logger.printMessage(Kind.NOTE, "Finished Processing Mocks"); + } + + /** + * Workaround to save the mocks for Eclipse's annotation processing framework which doesn't + * support the JavaFileObject object. + * @param clazz the class to save. + * @param outputFolderName the output folder where the class will be saved. + */ + private void saveMocksEclipse(GeneratedClassFile clazz, String outputFolderName) { + try { + FileUtils.saveClassToFolder(clazz, outputFolderName); + } catch (FileNotFoundException e) { + logger.printMessage(Kind.ERROR, e); + } catch (IOException e) { + logger.printMessage(Kind.ERROR, e); + } + } + + private AndroidMockGenerator getAndroidMockGenerator() { + return mockGenerator; + } +} diff --git a/tests/com/google/android/testing/mocking/AndroidFrameworkMockGeneratorTest.java b/tests/com/google/android/testing/mocking/AndroidFrameworkMockGeneratorTest.java new file mode 100644 index 0000000..813063f --- /dev/null +++ b/tests/com/google/android/testing/mocking/AndroidFrameworkMockGeneratorTest.java @@ -0,0 +1,165 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.testing.mocking; + +import javassist.CannotCompileException; +import javassist.ClassPool; +import javassist.CtClass; +import javassist.NotFoundException; + +import junit.framework.TestCase; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Vector; +import java.util.jar.JarEntry; + +/** + * @author swoodward@google.com (Stephen Woodward) + */ +public class AndroidFrameworkMockGeneratorTest extends TestCase { + private void cleanupGeneratedClasses(CtClass... classes) { + for (CtClass clazz : classes) { + clazz.detach(); + } + } + + private Collection<JarEntry> getMockJarEntries() { + JarEntry firstEntry = new JarEntry("java/lang/Object.class"); + JarEntry secondEntry = new JarEntry( + "com/google/android/testing/mocking/AndroidFrameworkMockGeneratorTest$Inner.class"); + List<JarEntry> entryList = new ArrayList<JarEntry>(); + entryList.add(firstEntry); + entryList.add(secondEntry); + return entryList; + } + + private <T> void assertUnorderedContentsSame(Iterable<T> expected, Iterable<T> actual) { + List<T> missingItems = new ArrayList<T>(); + List<T> extraItems = new ArrayList<T>(); + for (T item : expected) { + missingItems.add(item); + } + for (T item : actual) { + missingItems.remove(item); + extraItems.add(item); + } + for (T item : expected) { + extraItems.remove(item); + } + if (missingItems.size() + extraItems.size() != 0) { + String errorMessage = + "Contents were different. Missing: " + Arrays.toString(missingItems.toArray()) + + " Extra: " + Arrays.toString(extraItems.toArray()); + fail(errorMessage); + } + } + + private List<String> getClassNames(List<GeneratedClassFile> classes) { + List<String> classNames = new ArrayList<String>(); + for (GeneratedClassFile clazz : classes) { + classNames.add(clazz.getClassName()); + } + return classNames; + } + + private AndroidFrameworkMockGenerator getMockGenerator() { + return new AndroidFrameworkMockGenerator(); + } + + public void testCreateMockForClass() throws ClassNotFoundException, IOException, + CannotCompileException { + AndroidFrameworkMockGenerator mockGenerator = getMockGenerator(); + for (SdkVersion version : SdkVersion.getAllVersions()) { + List<GeneratedClassFile> classes = mockGenerator.createMocksForClass(Object.class, version); + + List<String> expectedNames = new ArrayList<String>(); + expectedNames.addAll(Arrays.asList(new String[] { + version.getPackagePrefix() + "genmocks.java.lang.ObjectDelegateSubclass", + version.getPackagePrefix() + "genmocks.java.lang.ObjectDelegateInterface"})); + List<String> actualNames = getClassNames(classes); + assertUnorderedContentsSame(expectedNames, actualNames); + } + } + + public void testGetClassList() throws ClassNotFoundException { + Collection<JarEntry> jarEntries = getMockJarEntries(); + List<String> expectedClassNames = + new ArrayList<String>(Arrays.asList(new String[] { + "java.lang.Object", + "com.google.android.testing.mocking.AndroidFrameworkMockGeneratorTest$Inner"})); + List<Class<?>> list = getMockGenerator().getClassList(jarEntries); + assertEquals(expectedClassNames.size(), list.size()); + for (Class<?> clazz : list) { + assertTrue(clazz.getName(), expectedClassNames.contains(clazz.getName())); + } + } + + public void testIsClassFile() { + assertTrue(getMockGenerator().jarEntryIsClassFile(new JarEntry("something.class"))); + assertTrue(getMockGenerator().jarEntryIsClassFile(new JarEntry("/Foo/Bar.class"))); + assertFalse(getMockGenerator().jarEntryIsClassFile(new JarEntry("/Foo/Bar.clas"))); + assertFalse(getMockGenerator().jarEntryIsClassFile(new JarEntry("/Foo/Bar.class "))); + assertFalse(getMockGenerator().jarEntryIsClassFile(new JarEntry("/Foo/Bar"))); + } + + public void testGetJarFileNameForVersion() { + for (SdkVersion version : SdkVersion.getAllVersions()) { + getMockGenerator(); + assertEquals("lib/android/android_" + version.getVersionName() + ".jar", + AndroidFrameworkMockGenerator.getJarFileNameForVersion(version)); + } + } + + public void testGetMocksForClass() throws ClassNotFoundException, IOException, + CannotCompileException, NotFoundException { + List<CtClass> createdClasses = new ArrayList<CtClass>(); + AndroidFrameworkMockGenerator mockGenerator = getMockGenerator(); + for (SdkVersion version : SdkVersion.getAllVersions()) { + List<GeneratedClassFile> createdMocks = mockGenerator.createMocksForClass( + Vector.class, version); + for (GeneratedClassFile mock : createdMocks) { + CtClass ctClass = ClassPool.getDefault().get(mock.getClassName()); + createdClasses.add(ctClass); + ctClass.toClass(); + } + } + List<GeneratedClassFile> mocks = mockGenerator.getMocksForClass(Vector.class); + String[] expectedClassNames = new String[] { + "v15.genmocks.java.util.VectorDelegateSubclass", + "v15.genmocks.java.util.VectorDelegateInterface", + "v16.genmocks.java.util.VectorDelegateSubclass", + "v16.genmocks.java.util.VectorDelegateInterface", + "v201.genmocks.java.util.VectorDelegateSubclass", + "v201.genmocks.java.util.VectorDelegateInterface", + "v21.genmocks.java.util.VectorDelegateSubclass", + "v21.genmocks.java.util.VectorDelegateInterface", + "v22.genmocks.java.util.VectorDelegateSubclass", + "v22.genmocks.java.util.VectorDelegateInterface" + }; + assertEquals(expectedClassNames.length, mocks.size()); + for (int i = 0; i < mocks.size(); ++i) { + assertEquals(expectedClassNames[i], mocks.get(i).getClassName()); + } + cleanupGeneratedClasses(createdClasses.toArray(new CtClass[0])); + } + + class Inner { + } +} diff --git a/tests/com/google/android/testing/mocking/AndroidMockGeneratorTest.java b/tests/com/google/android/testing/mocking/AndroidMockGeneratorTest.java new file mode 100644 index 0000000..ef36b24 --- /dev/null +++ b/tests/com/google/android/testing/mocking/AndroidMockGeneratorTest.java @@ -0,0 +1,575 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.android.testing.mocking; + +import javassist.CannotCompileException; +import javassist.ClassPool; +import javassist.CtClass; +import javassist.CtMethod; +import javassist.NotFoundException; + +import junit.framework.TestCase; + +import java.io.IOException; +import java.lang.reflect.Method; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + + +/** + * Tests for the AndroidMockGenerator class. + * + * @author swoodward@google.com (Stephen Woodward) + */ +public class AndroidMockGeneratorTest extends TestCase { + private AndroidMockGenerator getAndroidMockGenerator() { + return new AndroidMockGenerator(); + } + + private NoFileAndroidMockGenerator getNoFileMockGenerator() { + return new NoFileAndroidMockGenerator(); + } + + private void cleanupGeneratedClasses(CtClass... classes) { + for (CtClass clazz : classes) { + clazz.detach(); + } + } + + private <T> void assertUnorderedContentsSame(Iterable<T> expected, Iterable<T> actual) { + List<T> missingItems = new ArrayList<T>(); + List<T> extraItems = new ArrayList<T>(); + for (T item : expected) { + missingItems.add(item); + } + for (T item : actual) { + missingItems.remove(item); + extraItems.add(item); + } + for (T item : expected) { + extraItems.remove(item); + } + if (missingItems.size() + extraItems.size() != 0) { + String errorMessage = + "Contents were different. Missing: " + Arrays.toString(missingItems.toArray()) + + " Extra: " + Arrays.toString(extraItems.toArray()); + fail(errorMessage); + } + } + + private List<String> getExpectedNamesForNumberClass() { + return getExpectedNamesForNumberClass(false); + } + + private List<String> getExpectedNamesForObjectClass() { + List<String> expectedNames = new ArrayList<String>(); + expectedNames.addAll(Arrays.asList(new String[] {"clone", "finalize"})); + return expectedNames; + } + + private List<String> getExpectedNamesForNumberClass(boolean includeDelegateMethods) { + List<String> expectedNames = getExpectedNamesForObjectClass(); + expectedNames.addAll(Arrays.asList(new String[] {"byteValue", "doubleValue", "floatValue", + "intValue", "longValue", "shortValue"})); + if (includeDelegateMethods) { + expectedNames.addAll(Arrays.asList(new String[] {"getDelegate___AndroidMock", + "setDelegate___AndroidMock"})); + } + return expectedNames; + } + + private List<String> getExpectedNamesForBigIntegerClass() { + List<String> expectedNames = getExpectedNamesForNumberClass(); + expectedNames.addAll(Arrays.asList(new String[] {"abs", "add", "and", "andNot", "bitCount", + "bitLength", "clearBit", "compareTo", "divide", "divideAndRemainder", "flipBit", "gcd", + "getLowestSetBit", "isProbablePrime", "max", "min", "mod", "modInverse", "modPow", + "multiply", "negate", "nextProbablePrime", "not", "or", "pow", "remainder", "setBit", + "shiftLeft", "shiftRight", "signum", "subtract", "testBit", "toByteArray", "toString", + "xor"})); + return expectedNames; + } + + private List<String> getMethodNames(CtMethod[] methods) { + List<String> methodNames = new ArrayList<String>(); + for (CtMethod method : methods) { + methodNames.add(method.getName()); + } + return methodNames; + } + + private List<String> getClassNames(List<GeneratedClassFile> classes) { + List<String> classNames = new ArrayList<String>(); + for (GeneratedClassFile clazz : classes) { + classNames.add(clazz.getClassName()); + } + return classNames; + } + + private List<String> getExpectedSignaturesForBigIntegerClass() { + List<String> expectedNames = new ArrayList<String>(); + expectedNames.addAll(Arrays.asList(new String[] { + "public int java.math.BigInteger.getLowestSetBit()", + "public java.math.BigInteger java.math.BigInteger.abs()", + "protected void java.lang.Object.finalize() throws java.lang.Throwable", + "public java.math.BigInteger java.math.BigInteger.modPow(java.math.BigInteger," + + "java.math.BigInteger)", + "protected native java.lang.Object java.lang.Object.clone() throws " + + "java.lang.CloneNotSupportedException", + "public java.math.BigInteger java.math.BigInteger.setBit(int)", + "public java.math.BigInteger java.math.BigInteger.shiftRight(int)", + "public int java.math.BigInteger.bitLength()", + "public java.math.BigInteger java.math.BigInteger.not()", + "public java.math.BigInteger java.math.BigInteger.subtract(java.math.BigInteger)", + "public java.math.BigInteger java.math.BigInteger.flipBit(int)", + "public boolean java.math.BigInteger.isProbablePrime(int)", + "public java.math.BigInteger java.math.BigInteger.add(java.math.BigInteger)", + "public java.math.BigInteger java.math.BigInteger.modInverse(java.math.BigInteger)", + "public java.math.BigInteger java.math.BigInteger.clearBit(int)", + "public java.math.BigInteger java.math.BigInteger.multiply(java.math.BigInteger)", + "public byte java.lang.Number.byteValue()", + "public java.math.BigInteger java.math.BigInteger.gcd(java.math.BigInteger)", + "public float java.math.BigInteger.floatValue()", + "public java.lang.String java.math.BigInteger.toString(int)", + "public java.math.BigInteger java.math.BigInteger.min(java.math.BigInteger)", + "public int java.math.BigInteger.intValue()", + "public java.math.BigInteger java.math.BigInteger.or(java.math.BigInteger)", + "public java.math.BigInteger java.math.BigInteger.remainder(java.math.BigInteger)", + "public java.math.BigInteger java.math.BigInteger.divide(java.math.BigInteger)", + "public java.math.BigInteger java.math.BigInteger.xor(java.math.BigInteger)", + "public java.math.BigInteger java.math.BigInteger.and(java.math.BigInteger)", + "public int java.math.BigInteger.signum()", + "public java.math.BigInteger[] java.math.BigInteger.divideAndRemainder(" + + "java.math.BigInteger)", + "public java.math.BigInteger java.math.BigInteger.max(java.math.BigInteger)", + "public java.math.BigInteger java.math.BigInteger.shiftLeft(int)", + "public double java.math.BigInteger.doubleValue()", + "public java.math.BigInteger java.math.BigInteger.pow(int)", + "public short java.lang.Number.shortValue()", + "public java.math.BigInteger java.math.BigInteger.andNot(java.math.BigInteger)", + "public byte[] java.math.BigInteger.toByteArray()", + "public java.math.BigInteger java.math.BigInteger.negate()", + "public int java.math.BigInteger.compareTo(java.math.BigInteger)", + "public boolean java.math.BigInteger.testBit(int)", + "public int java.math.BigInteger.bitCount()", + "public long java.math.BigInteger.longValue()", + "public java.math.BigInteger java.math.BigInteger.mod(java.math.BigInteger)", + "public java.math.BigInteger java.math.BigInteger.nextProbablePrime()", + })); + return expectedNames; + } + + private List<String> getMethodSignatures(Method[] methods) { + List<String> methodSignatures = new ArrayList<String>(); + for (Method method : methods) { + if (getAndroidMockGenerator().isMockable(method)) { + methodSignatures.add(method.toGenericString()); + } + } + return methodSignatures; + } + + public void testIsSupportedType() { + Class<?>[] unsupportedClasses = + new Class[] {ClassIsAnnotation.class, ClassIsEnum.class, ClassIsFinal.class, + ClassIsInterface.class}; + Class<?>[] supportedClasses = new Class[] {Object.class}; + + for (Class<?> clazz : unsupportedClasses) { + assertFalse(getAndroidMockGenerator().classIsSupportedType(clazz)); + } + for (Class<?> clazz : supportedClasses) { + assertTrue(getAndroidMockGenerator().classIsSupportedType(clazz)); + } + } + + public void testGetDelegateFieldName() { + assertEquals("delegateMockObject", getAndroidMockGenerator().getDelegateFieldName()); + } + + public void testGetInterfaceMethodSource() throws SecurityException, NoSuchMethodException { + Method method = Object.class.getMethod("equals", Object.class); + assertEquals("public boolean equals(java.lang.Object arg0);", getAndroidMockGenerator() + .getInterfaceMethodSource(method)); + } + + public void testGetInterfaceMethodSourceMultipleExceptions() throws SecurityException, + NoSuchMethodException { + Method method = Class.class.getDeclaredMethod("newInstance"); + assertEquals("public java.lang.Object newInstance() throws java.lang.InstantiationException," + + "java.lang.IllegalAccessException;", getAndroidMockGenerator().getInterfaceMethodSource( + method)); + } + + public void testGetInterfaceMethodSourceProtectedMethod() throws SecurityException, + NoSuchMethodException { + Method method = Object.class.getDeclaredMethod("finalize"); + assertEquals("public void finalize() throws java.lang.Throwable;", getAndroidMockGenerator() + .getInterfaceMethodSource(method)); + } + + public void testGetInterfaceMethodSourceNoParams() throws SecurityException, + NoSuchMethodException { + Method method = Object.class.getMethod("toString"); + assertEquals("public java.lang.String toString();", getAndroidMockGenerator() + .getInterfaceMethodSource(method)); + } + + public void testGetInterfaceMethodSourceVoidReturn() throws SecurityException, + NoSuchMethodException { + Method method = Thread.class.getMethod("run"); + assertEquals("public void run();", getAndroidMockGenerator().getInterfaceMethodSource(method)); + } + + public void testGetInterfaceMethodSourceFinal() throws SecurityException, NoSuchMethodException { + Method method = Object.class.getMethod("notify"); + try { + getAndroidMockGenerator().getInterfaceMethodSource(method); + fail("Exception not thrown on a final method"); + } catch (UnsupportedOperationException e) { + // expected + } + } + + public void testGetInterfaceMethodSourceStatic() throws SecurityException, NoSuchMethodException { + Method method = Thread.class.getMethod("currentThread"); + try { + getAndroidMockGenerator().getInterfaceMethodSource(method); + fail("Exception not thrown on a static method"); + } catch (UnsupportedOperationException e) { + // expected + } + } + + public void testGetInterfaceName() { + AndroidMockGenerator r = getAndroidMockGenerator(); + assertEquals("genmocks.java.lang.ObjectDelegateInterface", + FileUtils.getInterfaceNameFor(Object.class, SdkVersion.UNKNOWN)); + } + + public void testGetSubclassName() { + AndroidMockGenerator r = getAndroidMockGenerator(); + assertEquals("genmocks.java.lang.ObjectDelegateSubclass", + FileUtils.getSubclassNameFor(Object.class, SdkVersion.UNKNOWN)); + } + + public void testGetDelegateMethodSource() throws SecurityException, NoSuchMethodException { + Method method = Object.class.getMethod("equals", Object.class); + assertEquals("public boolean equals(java.lang.Object arg0){if(this.delegateMockObject==null){" + + "return false;}return this.delegateMockObject.equals(arg0);}", getAndroidMockGenerator() + .getDelegateMethodSource(method)); + } + + public void testGetDelegateMethodSourceAllTypes() throws SecurityException, + NoSuchMethodException { + String[] returnTypes = + new String[] {"boolean", "byte", "short", "int", "long", "char", "float", "double"}; + String[] castTypes = + new String[] {"false", "(byte)0", "(short)0", "(int)0", "(long)0", "(char)0", "(float)0", + "(double)0"}; + for (int i = 0; i < returnTypes.length; ++i) { + Method method = AllTypes.class.getMethod(returnTypes[i] + "Foo"); + assertEquals("public " + returnTypes[i] + " " + returnTypes[i] + + "Foo(){if(this.delegateMockObject==null){return " + castTypes[i] + + ";}return this.delegateMockObject." + returnTypes[i] + "Foo();}", + getAndroidMockGenerator().getDelegateMethodSource(method)); + } + Method method = AllTypes.class.getMethod("objectFoo"); + assertEquals("public java.lang.Object objectFoo(){if(this.delegateMockObject==null){return " + + "null;}return this.delegateMockObject.objectFoo();}", getAndroidMockGenerator() + .getDelegateMethodSource(method)); + method = AllTypes.class.getMethod("voidFoo"); + assertEquals("public void voidFoo(){if(this.delegateMockObject==null){return ;" + + "}this.delegateMockObject.voidFoo();}", getAndroidMockGenerator() + .getDelegateMethodSource(method)); + } + + private class AllTypes { + @SuppressWarnings("unused") + public void voidFoo() { + } + + @SuppressWarnings("unused") + public boolean booleanFoo() { + return false; + } + + @SuppressWarnings("unused") + public byte byteFoo() { + return 0; + } + + @SuppressWarnings("unused") + public short shortFoo() { + return 0; + } + + @SuppressWarnings("unused") + public int intFoo() { + return 0; + } + + @SuppressWarnings("unused") + public long longFoo() { + return 0; + } + + @SuppressWarnings("unused") + public char charFoo() { + return 0; + } + + @SuppressWarnings("unused") + public float floatFoo() { + return 0; + } + + @SuppressWarnings("unused") + public double doubleFoo() { + return 0; + } + + @SuppressWarnings("unused") + public Object objectFoo() { + return null; + } + } + + public void testGetDelegateMethodSourceMultipleExceptions() throws SecurityException, + NoSuchMethodException { + Method method = Class.class.getDeclaredMethod("newInstance"); + assertEquals( + "public java.lang.Object newInstance() throws java.lang.InstantiationException," + + "java.lang.IllegalAccessException{if(this.delegateMockObject==null){return null;}" + + "return this.delegateMockObject.newInstance();}", getAndroidMockGenerator() + .getDelegateMethodSource(method)); + } + + public void testGetDelegateMethodSourceProtectedMethod() throws SecurityException, + NoSuchMethodException { + Method method = Object.class.getDeclaredMethod("finalize"); + assertEquals("public void finalize() throws java.lang.Throwable{if(this.delegateMockObject==" + + "null){return ;}this.delegateMockObject.finalize();}", getAndroidMockGenerator() + .getDelegateMethodSource(method)); + } + + public void testGetDelegateMethodSourceMultiParams() throws SecurityException, + NoSuchMethodException { + Method method = + String.class.getMethod("getChars", Integer.TYPE, Integer.TYPE, char[].class, Integer.TYPE); + assertEquals( + "public void getChars(int arg0,int arg1,char[] arg2,int arg3){if(this." + + "delegateMockObject==null){return ;}this.delegateMockObject.getChars(arg0,arg1,arg2," + + "arg3);}", getAndroidMockGenerator().getDelegateMethodSource(method)); + } + + public void testGetDelegateMethodSourceNoParams() throws SecurityException, + NoSuchMethodException { + Method method = Object.class.getMethod("toString"); + assertEquals( + "public java.lang.String toString(){if(this.delegateMockObject==null){return null;" + + "}return this.delegateMockObject.toString();}", getAndroidMockGenerator() + .getDelegateMethodSource(method)); + } + + public void testGetDelegateMethodSourceVoidReturn() throws SecurityException, + NoSuchMethodException { + Method method = Thread.class.getMethod("run"); + assertEquals("public void run(){if(this.delegateMockObject==null){return ;}this." + + "delegateMockObject.run();}", getAndroidMockGenerator().getDelegateMethodSource(method)); + } + + public void testGetDelegateMethodSourceFinal() throws SecurityException, NoSuchMethodException { + Method method = Object.class.getMethod("notify"); + try { + getAndroidMockGenerator().getDelegateMethodSource(method); + fail("Exception not thrown on a final method"); + } catch (UnsupportedOperationException e) { + // expected + } + } + + public void testGetDelegateMethodSourceStatic() throws SecurityException, NoSuchMethodException { + Method method = Thread.class.getMethod("currentThread"); + try { + getAndroidMockGenerator().getDelegateMethodSource(method); + fail("Exception not thrown on a static method"); + } catch (UnsupportedOperationException e) { + // expected + } + } + + public void testGenerateEmptySubclass() throws ClassNotFoundException, NotFoundException { + AndroidMockGenerator mockGenerator = getAndroidMockGenerator(); + CtClass generatedInterface = mockGenerator.generateInterface(String.class, SdkVersion.UNKNOWN); + CtClass generatedClass = getAndroidMockGenerator().generateSkeletalClass( + String.class, generatedInterface, SdkVersion.UNKNOWN); + + assertEquals("genmocks.java.lang", generatedClass.getPackageName()); + assertEquals("StringDelegateSubclass", generatedClass.getSimpleName()); + assertEquals("java.lang.String", generatedClass.getSuperclass().getName()); + cleanupGeneratedClasses(generatedInterface, generatedClass); + } + + public void testAddMethods() throws ClassNotFoundException { + AndroidMockGenerator mockGenerator = getAndroidMockGenerator(); + CtClass generatedInterface = mockGenerator.generateInterface(Number.class, SdkVersion.UNKNOWN); + CtClass generatedClass = + mockGenerator.generateSkeletalClass(Number.class, generatedInterface, SdkVersion.UNKNOWN); + + mockGenerator.addMethods(Number.class, generatedClass); + + List<String> expectedNames = getExpectedNamesForNumberClass(); + List<String> actualNames = getMethodNames(generatedClass.getDeclaredMethods()); + assertUnorderedContentsSame(expectedNames, actualNames); + cleanupGeneratedClasses(generatedInterface, generatedClass); + } + + public void testAddMethodsObjectClass() throws ClassNotFoundException { + AndroidMockGenerator mockGenerator = getAndroidMockGenerator(); + CtClass generatedInterface = mockGenerator.generateInterface(Object.class, SdkVersion.UNKNOWN); + CtClass generatedClass = + mockGenerator.generateSkeletalClass(Object.class, generatedInterface, SdkVersion.UNKNOWN); + + mockGenerator.addMethods(Object.class, generatedClass); + + List<String> expectedNames = getExpectedNamesForObjectClass(); + List<String> actualNames = getMethodNames(generatedClass.getDeclaredMethods()); + assertUnorderedContentsSame(expectedNames, actualNames); + cleanupGeneratedClasses(generatedInterface, generatedClass); + } + + public void testAddMethodsUsesSuperclass() throws ClassNotFoundException { + AndroidMockGenerator mockGenerator = getAndroidMockGenerator(); + CtClass generatedInterface = mockGenerator.generateInterface( + BigInteger.class, SdkVersion.UNKNOWN); + CtClass generatedClass = mockGenerator.generateSkeletalClass( + BigInteger.class, generatedInterface, SdkVersion.UNKNOWN); + + mockGenerator.addMethods(BigInteger.class, generatedClass); + + List<String> expectedNames = getExpectedNamesForBigIntegerClass(); + List<String> actualNames = getMethodNames(generatedClass.getDeclaredMethods()); + assertUnorderedContentsSame(expectedNames, actualNames); + cleanupGeneratedClasses(generatedInterface, generatedClass); + } + + public void testGetAllMethods() throws ClassNotFoundException { + AndroidMockGenerator mockGenerator = getAndroidMockGenerator(); + CtClass generatedInterface = mockGenerator.generateInterface( + BigInteger.class, SdkVersion.UNKNOWN); + CtClass generatedClass = mockGenerator.generateSkeletalClass( + BigInteger.class, generatedInterface, SdkVersion.UNKNOWN); + + Method[] methods = mockGenerator.getAllMethods(BigInteger.class); + + List<String> expectedNames = getExpectedSignaturesForBigIntegerClass(); + List<String> actualNames = getMethodSignatures(methods); + assertUnorderedContentsSame(expectedNames, actualNames); + cleanupGeneratedClasses(generatedInterface, generatedClass); + } + + public void testGenerateInterface() { + AndroidMockGenerator mockGenerator = getAndroidMockGenerator(); + CtClass generatedInterface = mockGenerator.generateInterface(Number.class, SdkVersion.UNKNOWN); + + List<String> expectedNames = getExpectedNamesForNumberClass(); + List<String> actualNames = getMethodNames(generatedInterface.getDeclaredMethods()); + assertUnorderedContentsSame(expectedNames, actualNames); + cleanupGeneratedClasses(generatedInterface); + } + + public void testAddInterfaceMethods() { + AndroidMockGenerator mockGenerator = getAndroidMockGenerator(); + CtClass generatedInterface = AndroidMockGenerator.getClassPool().makeInterface("testInterface"); + + mockGenerator.addInterfaceMethods(Number.class, generatedInterface); + + List<String> expectedNames = getExpectedNamesForNumberClass(); + List<String> actualNames = getMethodNames(generatedInterface.getDeclaredMethods()); + assertUnorderedContentsSame(expectedNames, actualNames); + cleanupGeneratedClasses(generatedInterface); + } + + public void testGenerateSubclass() throws ClassNotFoundException { + AndroidMockGenerator mockGenerator = getAndroidMockGenerator(); + CtClass generatedInterface = mockGenerator.generateInterface(Number.class, SdkVersion.UNKNOWN); + + CtClass generatedClass = + mockGenerator.generateSubClass(Number.class, generatedInterface, SdkVersion.UNKNOWN); + + List<String> expectedNames = getExpectedNamesForNumberClass(true); + List<String> actualNames = getMethodNames(generatedClass.getDeclaredMethods()); + assertUnorderedContentsSame(expectedNames, actualNames); + cleanupGeneratedClasses(generatedInterface, generatedClass); + } + + public void testCreateMockForClass() throws ClassNotFoundException, IOException, + CannotCompileException, NotFoundException { + NoFileAndroidMockGenerator mockGenerator = getNoFileMockGenerator(); + List<GeneratedClassFile> classes = mockGenerator.createMocksForClass(Object.class); + + List<String> expectedNames = new ArrayList<String>(); + String subclassName = "genmocks.java.lang.ObjectDelegateSubclass"; + String interfaceName = "genmocks.java.lang.ObjectDelegateInterface"; + expectedNames.addAll(Arrays.asList(new String[] {subclassName, + interfaceName})); + List<String> actualNames = getClassNames(classes); + assertUnorderedContentsSame(expectedNames, actualNames); + cleanupGeneratedClasses( + ClassPool.getDefault().get(subclassName), + ClassPool.getDefault().get(interfaceName)); + } + + public void testGetSetDelegateMethodSource() { + AndroidMockGenerator mockGenerator = getAndroidMockGenerator(); + CtClass generatedInterface = mockGenerator.generateInterface(Object.class, SdkVersion.UNKNOWN); + String expectedSource = + "public void setDelegate___AndroidMock(genmocks.java.lang.ObjectDelegateInterface obj) {" + + " this.delegateMockObject = obj;}"; + + assertEquals(expectedSource, mockGenerator.getSetDelegateMethodSource(generatedInterface)); + } + + public void testIsForbiddenMethod() throws SecurityException, NoSuchMethodException { + Method[] forbiddenMethods = + new Method[] {Object.class.getMethod("equals", Object.class), + Object.class.getMethod("toString"), Object.class.getMethod("hashCode")}; + Method[] allowedMethods = new Method[] {BigInteger.class.getMethod("toString", Integer.TYPE)}; + for (Method method : forbiddenMethods) { + assertTrue(getAndroidMockGenerator().isForbiddenMethod(method)); + } + for (Method method : allowedMethods) { + assertFalse(getAndroidMockGenerator().isForbiddenMethod(method)); + } + } + + /** + * Support test class for capturing the names of files that would have been + * saved to a jar file. + * + * @author swoodward@google.com (Stephen Woodward) + */ + class NoFileAndroidMockGenerator extends AndroidMockGenerator { + List<CtClass> savedClasses = new ArrayList<CtClass>(); + + @Override + void saveCtClass(CtClass clazz) { + savedClasses.add(clazz); + } + } +} diff --git a/tests/com/google/android/testing/mocking/AndroidMockTest.java b/tests/com/google/android/testing/mocking/AndroidMockTest.java new file mode 100644 index 0000000..56fcbb3 --- /dev/null +++ b/tests/com/google/android/testing/mocking/AndroidMockTest.java @@ -0,0 +1,927 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.android.testing.mocking; + +import javassist.CannotCompileException; +import javassist.ClassPool; +import javassist.CtClass; +import javassist.CtMethod; +import javassist.Modifier; +import javassist.NotFoundException; +import javassist.expr.ExprEditor; +import javassist.expr.MethodCall; + +import junit.framework.TestCase; + +import org.easymock.Capture; +import org.easymock.IAnswer; +import org.easymock.LogicalOperator; +import org.easymock.internal.matchers.Equals; + +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInput; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.SimpleTimeZone; +import java.util.Vector; + + +/** + * Tests for the AndroidMock class. + * + * @author swoodward@google.com (Stephen Woodward) + */ +public class AndroidMockTest extends TestCase { + private List<String> notForwardedMethods = + new ArrayList<String>(Arrays.asList(new String[] { + "com.google.android.testing.mocking.AndroidMock.getInterfaceFor(java.lang.Class)", + "com.google.android.testing.mocking.AndroidMock.getSubclassNameFor(java.lang.Class)", + "com.google.android.testing.mocking.AndroidMock.getSubclassFor(java.lang.Class," + + "java.lang.Class,java.lang.Object)", + "com.google.android.testing.mocking.AndroidMock.getInterfaceNameFor(java.lang.Class)", + "com.google.android.testing.mocking.AndroidMock.createStrictMock(" + + "java.lang.Class,java.lang.Object[])", + "com.google.android.testing.mocking.AndroidMock.createStrictMock(" + + "java.lang.String,java.lang.Class,java.lang.Object[])", + "com.google.android.testing.mocking.AndroidMock.createMock(" + + "java.lang.Class,java.lang.Object[])", + "com.google.android.testing.mocking.AndroidMock.createMock(" + + "java.lang.String,java.lang.Class,java.lang.Object[])", + "com.google.android.testing.mocking.AndroidMock.createNiceMock(" + + "java.lang.Class,java.lang.Object[])", + "com.google.android.testing.mocking.AndroidMock.createNiceMock(" + + "java.lang.String,java.lang.Class,java.lang.Object[])"})); + + private CtMethod[] getForwardedMethods() throws NotFoundException { + List<CtMethod> methods = + new ArrayList<CtMethod>(Arrays.asList(getAndroidMockCtClass().getDeclaredMethods())); + // Get a copy for safe removal of elements during iteration. + for (CtMethod method : Arrays.asList(methods.toArray(new CtMethod[0]))) { + if (notForwardedMethods.contains(method.getLongName()) + || !Modifier.isPublic(method.getModifiers())) { + methods.remove(method); + } + } + return methods.toArray(new CtMethod[0]); + } + + private CtClass getAndroidMockCtClass() throws NotFoundException { + return ClassPool.getDefault().get("com.google.android.testing.mocking.AndroidMock"); + } + + private void compileClasses(List<GeneratedClassFile> mockClasses) throws NotFoundException { + for (GeneratedClassFile clazz : mockClasses) { + CtClass ctClass; + ctClass = ClassPool.getDefault().get(clazz.getClassName()); + try { + ctClass.toClass(); + } catch (CannotCompileException e) { + // Just ignore -- this will happen for every class used in more than one test. + } + } + } + + public void testIsUnboxableToPrimitiveAllPrimitives() { + assertTrue(AndroidMock.isUnboxableToPrimitive(Integer.TYPE, new Integer(42), true)); + assertTrue(AndroidMock.isUnboxableToPrimitive(Long.TYPE, new Long(42L), true)); + assertTrue(AndroidMock.isUnboxableToPrimitive(Short.TYPE, new Short((short) 42), true)); + assertTrue(AndroidMock.isUnboxableToPrimitive(Byte.TYPE, new Byte((byte) 42), true)); + assertTrue(AndroidMock.isUnboxableToPrimitive(Boolean.TYPE, Boolean.TRUE, true)); + assertTrue(AndroidMock.isUnboxableToPrimitive(Float.TYPE, new Float(42.0f), true)); + assertTrue(AndroidMock.isUnboxableToPrimitive(Double.TYPE, new Double(42.0), true)); + assertTrue(AndroidMock.isUnboxableToPrimitive(Character.TYPE, new Character('a'), true)); + + assertTrue(AndroidMock.isUnboxableToPrimitive(Integer.TYPE, 42, true)); + assertTrue(AndroidMock.isUnboxableToPrimitive(Long.TYPE, 42L, true)); + assertTrue(AndroidMock.isUnboxableToPrimitive(Short.TYPE, (short) 42, true)); + assertTrue(AndroidMock.isUnboxableToPrimitive(Byte.TYPE, (byte) 42, true)); + assertTrue(AndroidMock.isUnboxableToPrimitive(Boolean.TYPE, true, true)); + assertTrue(AndroidMock.isUnboxableToPrimitive(Float.TYPE, 42.0f, true)); + assertTrue(AndroidMock.isUnboxableToPrimitive(Double.TYPE, 42.0, true)); + assertTrue(AndroidMock.isUnboxableToPrimitive(Character.TYPE, 'a', true)); + } + + public void testIsUnboxableToPrimitiveIsObject() { + assertFalse(AndroidMock.isUnboxableToPrimitive(Integer.TYPE, new Object(), false)); + } + + public void testIsUnboxableToPrimitiveAllWideningPrimitives() { + Object[] testValues = + new Object[] {new Byte((byte) 42), new Short((short) 42), new Integer(42), new Long(42L), + new Float(42.0f), new Double(42.0), new Character('a'), Boolean.TRUE}; + boolean[] byteExpected = new boolean[] {true, false, false, false, false, false, false, false}; + boolean[] shortExpected = new boolean[] {true, true, false, false, false, false, true, false}; + boolean[] intExpected = new boolean[] {true, true, true, false, false, false, true, false}; + boolean[] longExpected = new boolean[] {true, true, true, true, false, false, true, false}; + boolean[] floatExpected = new boolean[] {true, true, true, false, true, false, true, false}; + boolean[] doubleExpected = new boolean[] {true, true, true, true, true, true, true, false}; + boolean[] charExpected = new boolean[] {true, true, true, false, false, false, true, false}; + boolean[] booleanExpected = + new boolean[] {false, false, false, false, false, false, false, true}; + + for (int i = 0; i < testValues.length; ++i) { + assertEquals("Convert byte from " + testValues[i].getClass(), byteExpected[i], AndroidMock + .isUnboxableToPrimitive(Byte.TYPE, testValues[i], false)); + assertEquals("Convert short from " + testValues[i].getClass(), shortExpected[i], AndroidMock + .isUnboxableToPrimitive(Short.TYPE, testValues[i], false)); + assertEquals("Convert int from " + testValues[i].getClass(), intExpected[i], AndroidMock + .isUnboxableToPrimitive(Integer.TYPE, testValues[i], false)); + assertEquals("Convert long from " + testValues[i].getClass(), longExpected[i], AndroidMock + .isUnboxableToPrimitive(Long.TYPE, testValues[i], false)); + assertEquals("Convert float from " + testValues[i].getClass(), floatExpected[i], AndroidMock + .isUnboxableToPrimitive(Float.TYPE, testValues[i], false)); + assertEquals("Convert double from " + testValues[i].getClass(), doubleExpected[i], + AndroidMock.isUnboxableToPrimitive(Double.TYPE, testValues[i], false)); + assertEquals("Convert char from " + testValues[i].getClass(), charExpected[i], AndroidMock + .isUnboxableToPrimitive(Character.TYPE, testValues[i], false)); + assertEquals("Convert boolean from " + testValues[i].getClass(), booleanExpected[i], + AndroidMock.isUnboxableToPrimitive(Boolean.TYPE, testValues[i], false)); + } + } + + + public void testIsUnboxableToPrimitiveNotPrimitive() { + try { + AndroidMock.isUnboxableToPrimitive(Object.class, Object.class, false); + fail("Exception should have been thrown"); + } catch (IllegalArgumentException e) { + // expected + } + } + + public void testCreateMock() throws ClassNotFoundException, IOException, CannotCompileException, + NotFoundException { + List<GeneratedClassFile> mockClasses = + new AndroidMockGenerator().createMocksForClass(Vector.class); + compileClasses(mockClasses); + Vector<String> mockVector = AndroidMock.createMock(Vector.class); + AndroidMock.expect(mockVector.get(0)).andReturn("Hello World"); + AndroidMock.replay(mockVector); + assertEquals("Hello World", mockVector.get(0).toString()); + AndroidMock.verify(mockVector); + } + + public void testCreateMockUsingParameters() throws ClassNotFoundException, IOException, + CannotCompileException, NotFoundException { + List<GeneratedClassFile> mockClasses = + new AndroidMockGenerator().createMocksForClass(SimpleTimeZone.class); + compileClasses(mockClasses); + SimpleTimeZone mockTimeZone = AndroidMock.createMock(SimpleTimeZone.class, 0, "GMT"); + AndroidMock.expect(mockTimeZone.getRawOffset()).andReturn(42); + AndroidMock.replay(mockTimeZone); + assertEquals(42, mockTimeZone.getRawOffset()); + AndroidMock.verify(mockTimeZone); + } + + public void testCreateMockUsingProtectedConstructors() throws ClassNotFoundException, + IOException, CannotCompileException, NotFoundException { + List<GeneratedClassFile> mockClasses = + new AndroidMockGenerator().createMocksForClass(Calendar.class); + compileClasses(mockClasses); + Calendar mockCalendar = AndroidMock.createMock(Calendar.class); + AndroidMock.expect(mockCalendar.getGreatestMinimum(1)).andReturn(42); + AndroidMock.replay(mockCalendar); + assertEquals(42, mockCalendar.getGreatestMinimum(1)); + AndroidMock.verify(mockCalendar); + + // Just don't explode + Calendar newMockCalendar = + AndroidMock.createMock(Calendar.class, new SimpleTimeZone(1, "GMT"), Locale.UK); + } + + public void testCreateMockUsingCastableParameters() throws ClassNotFoundException, IOException, + CannotCompileException, NotFoundException { + List<GeneratedClassFile> mockClasses = + new AndroidMockGenerator().createMocksForClass(SimpleTimeZone.class); + compileClasses(mockClasses); + SimpleTimeZone mockTimeZone = AndroidMock.createMock(SimpleTimeZone.class, 'a', "GMT"); + AndroidMock.expect(mockTimeZone.getRawOffset()).andReturn(42); + AndroidMock.replay(mockTimeZone); + assertEquals(42, mockTimeZone.getRawOffset()); + AndroidMock.verify(mockTimeZone); + } + + public void testCreateMockUsingUnusableParameters() throws ClassNotFoundException, IOException, + CannotCompileException, NotFoundException { + List<GeneratedClassFile> mockClasses = + new AndroidMockGenerator().createMocksForClass(SimpleTimeZone.class); + compileClasses(mockClasses); + try { + SimpleTimeZone mockTimeZone = AndroidMock.createMock(SimpleTimeZone.class, "GMT"); + fail("Excepted an IllegalArgumentException for incorrect number of constructor parameters"); + } catch (IllegalArgumentException e) { + // Expected + } + try { + SimpleTimeZone mockTimeZone = AndroidMock.createMock(SimpleTimeZone.class, 0, null); + fail("Excepted an IllegalArgumentException for indeterminate null constructor parameters"); + } catch (IllegalArgumentException e) { + // Expected + } + try { + SimpleTimeZone mockTimeZone = AndroidMock.createMock(SimpleTimeZone.class, 0, new Object()); + fail("Excepted an IllegalArgumentException for incorrect constructor parameters"); + } catch (IllegalArgumentException e) { + // Expected + } + } + + public void testCreateMockUsingInterface() throws ClassNotFoundException, IOException, + CannotCompileException, NotFoundException { + List<GeneratedClassFile> mockClasses = + new AndroidMockGenerator().createMocksForClass(Map.class); + compileClasses(mockClasses); + Map<String, String> mockMap = AndroidMock.createMock(Map.class); + AndroidMock.expect(mockMap.get("key")).andReturn("Hello World"); + AndroidMock.replay(mockMap); + assertEquals("Hello World", mockMap.get("key")); + AndroidMock.verify(mockMap); + } + + public void testCreateMockUsingClass() throws ClassNotFoundException, IOException, + CannotCompileException, NotFoundException { + List<GeneratedClassFile> mockClasses = + new AndroidMockGenerator().createMocksForClass(Vector.class); + compileClasses(mockClasses); + Vector<String> mockVector = AndroidMock.createMock(Vector.class); + AndroidMock.expect(mockVector.get(0)).andReturn("Hello World"); + AndroidMock.replay(mockVector); + assertEquals("Hello World", mockVector.get(0).toString()); + AndroidMock.verify(mockVector); + } + + public void testCreateNiceMock() throws ClassNotFoundException, IOException, + CannotCompileException, NotFoundException { + List<GeneratedClassFile> mockClasses = + new AndroidMockGenerator().createMocksForClass(Vector.class); + compileClasses(mockClasses); + Vector<String> mockVector = AndroidMock.createNiceMock(Vector.class); + AndroidMock.expect(mockVector.get(0)).andReturn("Hello World"); + AndroidMock.replay(mockVector); + assertEquals("Hello World", mockVector.get(0).toString()); + AndroidMock.verify(mockVector); + } + + public void testCreateNiceMockUsingUnusableParameters() throws ClassNotFoundException, + IOException, CannotCompileException, NotFoundException { + List<GeneratedClassFile> mockClasses = + new AndroidMockGenerator().createMocksForClass(SimpleTimeZone.class); + compileClasses(mockClasses); + try { + SimpleTimeZone mockTimeZone = AndroidMock.createNiceMock(SimpleTimeZone.class, "GMT"); + fail("Excepted an IllegalArgumentException for incorrect number of constructor parameters"); + } catch (IllegalArgumentException e) { + // Expected + } + try { + SimpleTimeZone mockTimeZone = AndroidMock.createNiceMock(SimpleTimeZone.class, 0, null); + fail("Excepted an IllegalArgumentException for indeterminate null constructor parameters"); + } catch (IllegalArgumentException e) { + // Expected + } + try { + SimpleTimeZone mockTimeZone = + AndroidMock.createNiceMock(SimpleTimeZone.class, 0, new Object()); + fail("Excepted an IllegalArgumentException for incorrect constructor parameters"); + } catch (IllegalArgumentException e) { + // Expected + } + } + + public void testCreateNiceMockUsingParameters() throws ClassNotFoundException, IOException, + CannotCompileException, NotFoundException { + List<GeneratedClassFile> mockClasses = + new AndroidMockGenerator().createMocksForClass(SimpleTimeZone.class); + + compileClasses(mockClasses); + SimpleTimeZone mockTimeZone = AndroidMock.createNiceMock(SimpleTimeZone.class, 0, "GMT"); + AndroidMock.expect(mockTimeZone.getRawOffset()).andReturn(42); + AndroidMock.replay(mockTimeZone); + assertEquals(42, mockTimeZone.getRawOffset()); + AndroidMock.verify(mockTimeZone); + } + + public void testCreateNiceMockUsingCastableParameters() throws ClassNotFoundException, + IOException, CannotCompileException, NotFoundException { + List<GeneratedClassFile> mockClasses = + new AndroidMockGenerator().createMocksForClass(SimpleTimeZone.class); + + compileClasses(mockClasses); + SimpleTimeZone mockTimeZone = AndroidMock.createNiceMock(SimpleTimeZone.class, 'a', "GMT"); + AndroidMock.expect(mockTimeZone.getRawOffset()).andReturn(42); + AndroidMock.replay(mockTimeZone); + assertEquals(42, mockTimeZone.getRawOffset()); + AndroidMock.verify(mockTimeZone); + } + + public void testCreateNiceMockUsingInterface() throws ClassNotFoundException, IOException, + CannotCompileException, NotFoundException { + List<GeneratedClassFile> mockClasses = + new AndroidMockGenerator().createMocksForClass(Map.class); + + compileClasses(mockClasses); + Map<String, String> mockMap = AndroidMock.createNiceMock(Map.class); + AndroidMock.expect(mockMap.get("key")).andReturn("Hello World"); + AndroidMock.replay(mockMap); + assertEquals("Hello World", mockMap.get("key")); + AndroidMock.verify(mockMap); + } + + public void testCreateNiceMockUsingClass() throws ClassNotFoundException, IOException, + CannotCompileException, NotFoundException { + List<GeneratedClassFile> mockClasses = + new AndroidMockGenerator().createMocksForClass(Vector.class); + + compileClasses(mockClasses); + Vector<String> mockVector = AndroidMock.createNiceMock(Vector.class); + AndroidMock.expect(mockVector.get(0)).andReturn("Hello World"); + AndroidMock.replay(mockVector); + assertEquals("Hello World", mockVector.get(0).toString()); + AndroidMock.verify(mockVector); + } + + public void testCreateStrictMock() throws ClassNotFoundException, IOException, + CannotCompileException, NotFoundException { + List<GeneratedClassFile> mockClasses = + new AndroidMockGenerator().createMocksForClass(Vector.class); + + compileClasses(mockClasses); + Vector<String> mockVector = AndroidMock.createStrictMock(Vector.class); + AndroidMock.expect(mockVector.get(0)).andReturn("Hello World"); + AndroidMock.replay(mockVector); + assertEquals("Hello World", mockVector.get(0).toString()); + AndroidMock.verify(mockVector); + } + + public void testCreateStrictMockUsingUnusableParameters() throws ClassNotFoundException, + IOException, CannotCompileException, NotFoundException { + List<GeneratedClassFile> mockClasses = + new AndroidMockGenerator().createMocksForClass(SimpleTimeZone.class); + + compileClasses(mockClasses); + try { + SimpleTimeZone mockTimeZone = AndroidMock.createStrictMock(SimpleTimeZone.class, "GMT"); + fail("Excepted an IllegalArgumentException for incorrect number of constructor parameters"); + } catch (IllegalArgumentException e) { + // Expected + } + try { + SimpleTimeZone mockTimeZone = AndroidMock.createStrictMock(SimpleTimeZone.class, 0, null); + fail("Excepted an IllegalArgumentException for indeterminate null constructor parameters"); + } catch (IllegalArgumentException e) { + // Expected + } + try { + SimpleTimeZone mockTimeZone = + AndroidMock.createStrictMock(SimpleTimeZone.class, 0, new Object()); + fail("Excepted an IllegalArgumentException for incorrect constructor parameters"); + } catch (IllegalArgumentException e) { + // Expected + } + } + + public void testCreateStrictMockUsingParameters() throws ClassNotFoundException, IOException, + CannotCompileException, NotFoundException { + List<GeneratedClassFile> mockClasses = + new AndroidMockGenerator().createMocksForClass(SimpleTimeZone.class); + + compileClasses(mockClasses); + SimpleTimeZone mockTimeZone = AndroidMock.createStrictMock(SimpleTimeZone.class, 0, "GMT"); + AndroidMock.expect(mockTimeZone.getRawOffset()).andReturn(42); + AndroidMock.replay(mockTimeZone); + assertEquals(42, mockTimeZone.getRawOffset()); + AndroidMock.verify(mockTimeZone); + } + + public void testCreateStrictMockUsingCastableParameters() throws ClassNotFoundException, + IOException, CannotCompileException, NotFoundException { + List<GeneratedClassFile> mockClasses = + new AndroidMockGenerator().createMocksForClass(SimpleTimeZone.class); + + compileClasses(mockClasses); + SimpleTimeZone mockTimeZone = AndroidMock.createStrictMock(SimpleTimeZone.class, 'a', "GMT"); + AndroidMock.expect(mockTimeZone.getRawOffset()).andReturn(42); + AndroidMock.replay(mockTimeZone); + assertEquals(42, mockTimeZone.getRawOffset()); + AndroidMock.verify(mockTimeZone); + } + + public void testCreateStrictMockUsingInterface() throws ClassNotFoundException, IOException, + CannotCompileException, NotFoundException { + List<GeneratedClassFile> mockClasses = + new AndroidMockGenerator().createMocksForClass(Map.class); + + compileClasses(mockClasses); + Map<String, String> mockMap = AndroidMock.createStrictMock(Map.class); + AndroidMock.expect(mockMap.get("key")).andReturn("Hello World"); + AndroidMock.replay(mockMap); + assertEquals("Hello World", mockMap.get("key")); + AndroidMock.verify(mockMap); + } + + public void testCreateStrictMockUsingClass() throws ClassNotFoundException, IOException, + CannotCompileException, NotFoundException { + List<GeneratedClassFile> mockClasses = + new AndroidMockGenerator().createMocksForClass(Vector.class); + compileClasses(mockClasses); + Vector<String> mockVector = AndroidMock.createStrictMock(Vector.class); + AndroidMock.expect(mockVector.get(0)).andReturn("Hello World"); + AndroidMock.replay(mockVector); + assertEquals("Hello World", mockVector.get(0).toString()); + AndroidMock.verify(mockVector); + } + + public void testCreateMockConstructorDoesWorkOnAllReturnTypes() throws ClassNotFoundException, + IOException, CannotCompileException, NotFoundException { + List<GeneratedClassFile> mockClasses = + new AndroidMockGenerator().createMocksForClass(ClassDoesWorkInConstructor.class); + compileClasses(mockClasses); + ClassDoesWorkInConstructor mock = AndroidMock.createMock(ClassDoesWorkInConstructor.class); + } + + public void testAllForwardedMethods() throws CannotCompileException, NotFoundException { + for (CtMethod method : getForwardedMethods()) { + MethodVerifier verifier = new MethodVerifier(method); + // CtMethod.instrument Causes every instruction in the method to be + // inspected, and passed to + // the MethodVerifier callback (extends javassist.expr.ExprEditor). We + // want to verify that + // the expected EasyMock method is called at least once in each + // AndroidMock method. + method.instrument(verifier); + assertTrue(method.getLongName() + " not called.", verifier.expectedMethodCalled()); + } + } + + public void testCheckOrder() throws ClassNotFoundException, IOException, CannotCompileException, + NotFoundException { + List<GeneratedClassFile> mockClasses = + new AndroidMockGenerator().createMocksForClass(Vector.class); + compileClasses(mockClasses); + Vector<?> mockVector = AndroidMock.createMock(Vector.class); + AndroidMock.checkOrder(mockVector, false); + AndroidMock.checkOrder(AndroidMock.createMock(Map.class), false); + } + + public void testVerify() throws ClassNotFoundException, IOException, CannotCompileException, + NotFoundException { + List<GeneratedClassFile> mockClasses = + new AndroidMockGenerator().createMocksForClass(Vector.class); + compileClasses(mockClasses); + Vector<?> mockVector = AndroidMock.createMock(Vector.class); + AndroidMock.replay(mockVector); + AndroidMock.verify(mockVector); + Map<?, ?> mockMap = AndroidMock.createMock(Map.class); + AndroidMock.replay(mockMap); + AndroidMock.verify(mockMap); + } + + public void testResetToStrict() throws ClassNotFoundException, IOException, + CannotCompileException, NotFoundException { + List<GeneratedClassFile> mockClasses = + new AndroidMockGenerator().createMocksForClass(Vector.class); + compileClasses(mockClasses); + Vector<?> mockVector = AndroidMock.createMock(Vector.class); + AndroidMock.resetToStrict(mockVector); + Map<?, ?> mockMap = AndroidMock.createMock(Map.class); + AndroidMock.resetToStrict(mockMap); + } + + public void testResetToDefault() throws ClassNotFoundException, IOException, + CannotCompileException, NotFoundException { + List<GeneratedClassFile> mockClasses = + new AndroidMockGenerator().createMocksForClass(Vector.class); + compileClasses(mockClasses); + Vector<?> mockVector = AndroidMock.createMock(Vector.class); + AndroidMock.resetToDefault(mockVector); + Map<?, ?> mockMap = AndroidMock.createMock(Map.class); + AndroidMock.resetToDefault(mockMap); + } + + public void testResetToNice() throws ClassNotFoundException, IOException, + CannotCompileException, NotFoundException { + List<GeneratedClassFile> mockClasses = + new AndroidMockGenerator().createMocksForClass(Vector.class); + compileClasses(mockClasses); + Vector<?> mockVector = AndroidMock.createMock(Vector.class); + AndroidMock.resetToNice(mockVector); + Map<?, ?> mockMap = AndroidMock.createMock(Map.class); + AndroidMock.resetToNice(mockMap); + } + + public void testReset() throws ClassNotFoundException, IOException, CannotCompileException, + NotFoundException { + List<GeneratedClassFile> mockClasses = + new AndroidMockGenerator().createMocksForClass(Vector.class); + compileClasses(mockClasses); + Vector<?> mockVector = AndroidMock.createMock(Vector.class); + AndroidMock.reset(mockVector); + Map<?, ?> mockMap = AndroidMock.createMock(Map.class); + AndroidMock.reset(mockMap); + + } + + public void testReplay() throws ClassNotFoundException, IOException, CannotCompileException, + NotFoundException { + List<GeneratedClassFile> mockClasses = + new AndroidMockGenerator().createMocksForClass(Vector.class); + compileClasses(mockClasses); + Vector<?> mockVector = AndroidMock.createMock(Vector.class); + AndroidMock.replay(mockVector); + Map<?, ?> mockMap = AndroidMock.createMock(Map.class); + AndroidMock.replay(mockMap); + } + + public void testExpect() { + Map<?, ?> mockMap = AndroidMock.createMock(Map.class); + mockMap.clear(); + AndroidMock.expect(null); + AndroidMock.replay(mockMap); + } + + public void testExpectLastCall() { + Map<?, ?> mockMap = AndroidMock.createMock(Map.class); + mockMap.clear(); + AndroidMock.expectLastCall(); + AndroidMock.replay(mockMap); + } + + public void testAnyBoolean() { + Map<?, ?> mockMap = AndroidMock.createMock(Map.class); + AndroidMock.expect(mockMap.get(AndroidMock.anyBoolean())).andReturn(null); + AndroidMock.replay(mockMap); + } + + public void testAnyByte() { + Map<?, ?> mockMap = AndroidMock.createMock(Map.class); + AndroidMock.expect(mockMap.get(AndroidMock.anyByte())).andReturn(null); + AndroidMock.replay(mockMap); + } + + public void testAnyChar() { + Map<?, ?> mockMap = AndroidMock.createMock(Map.class); + AndroidMock.expect(mockMap.get(AndroidMock.anyChar())).andReturn(null); + AndroidMock.replay(mockMap); + } + + public void testAnyInt() { + Map<?, ?> mockMap = AndroidMock.createMock(Map.class); + AndroidMock.expect(mockMap.get(AndroidMock.anyInt())).andReturn(null); + AndroidMock.replay(mockMap); + } + + public void testAnyLong() { + Map<?, ?> mockMap = AndroidMock.createMock(Map.class); + AndroidMock.expect(mockMap.get(AndroidMock.anyLong())).andReturn(null); + AndroidMock.replay(mockMap); + } + + public void testAnyFloat() { + Map<?, ?> mockMap = AndroidMock.createMock(Map.class); + AndroidMock.expect(mockMap.get(AndroidMock.anyFloat())).andReturn(null); + AndroidMock.replay(mockMap); + } + + public void testAnyDouble() { + Map<?, ?> mockMap = AndroidMock.createMock(Map.class); + AndroidMock.expect(mockMap.get(AndroidMock.anyDouble())).andReturn(null); + AndroidMock.replay(mockMap); + } + + public void testAnyShort() { + Map<?, ?> mockMap = AndroidMock.createMock(Map.class); + AndroidMock.expect(mockMap.get(AndroidMock.anyShort())).andReturn(null); + AndroidMock.replay(mockMap); + } + + public void testAnyObject() { + Map<?, ?> mockMap = AndroidMock.createMock(Map.class); + AndroidMock.expect(mockMap.get(AndroidMock.anyObject())).andReturn(null); + AndroidMock.replay(mockMap); + } + + public void testGeq() { + Map<?, ?> mockMap = AndroidMock.createMock(Map.class); + AndroidMock.expect(mockMap.get(AndroidMock.geq((byte) 0))).andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.geq((short) 0))).andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.geq(0))).andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.geq(0L))).andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.geq(0.0))).andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.geq(0.0f))).andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.geq("Hi"))).andReturn(null); + AndroidMock.replay(mockMap); + } + + public void testLeq() { + Map<?, ?> mockMap = AndroidMock.createMock(Map.class); + AndroidMock.expect(mockMap.get(AndroidMock.leq((byte) 0))).andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.leq((short) 0))).andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.leq(0))).andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.leq(0L))).andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.leq(0.0))).andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.leq(0.0f))).andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.leq("Hi"))).andReturn(null); + AndroidMock.replay(mockMap); + } + + public void testGt() { + Map<?, ?> mockMap = AndroidMock.createMock(Map.class); + AndroidMock.expect(mockMap.get(AndroidMock.gt((byte) 0))).andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.gt((short) 0))).andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.gt(0))).andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.gt(0L))).andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.gt(0.0))).andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.gt(0.0f))).andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.gt("Hi"))).andReturn(null); + AndroidMock.replay(mockMap); + } + + public void testLt() { + Map<?, ?> mockMap = AndroidMock.createMock(Map.class); + AndroidMock.expect(mockMap.get(AndroidMock.lt((byte) 0))).andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.lt((short) 0))).andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.lt(0))).andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.lt(0L))).andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.lt(0.0))).andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.lt(0.0f))).andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.lt("Hi"))).andReturn(null); + AndroidMock.replay(mockMap); + } + + public void testIsA() { + Map<?, ?> mockMap = AndroidMock.createMock(Map.class); + AndroidMock.expect(mockMap.get(AndroidMock.isA(String.class))).andReturn(null); + AndroidMock.replay(mockMap); + } + + public void testContains() { + Map<?, ?> mockMap = AndroidMock.createMock(Map.class); + AndroidMock.expect(mockMap.get(AndroidMock.contains("hi"))).andReturn(null); + AndroidMock.replay(mockMap); + } + + public void testAnd() { + Map<?, ?> mockMap = AndroidMock.createMock(Map.class); + AndroidMock.expect(mockMap.get(AndroidMock.and(AndroidMock.eq(true), AndroidMock.eq(true)))) + .andReturn(null); + AndroidMock.expect( + mockMap.get(AndroidMock.and(AndroidMock.eq((byte) 0), AndroidMock.eq((byte) 0)))) + .andReturn(null); + AndroidMock.expect( + mockMap.get(AndroidMock.and(AndroidMock.eq((short) 0), AndroidMock.eq((short) 0)))) + .andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.and(AndroidMock.eq(0), AndroidMock.eq(0)))) + .andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.and(AndroidMock.eq(0L), AndroidMock.eq(0L)))) + .andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.and(AndroidMock.eq(0.0), AndroidMock.eq(0.0)))) + .andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.and(AndroidMock.eq(0.0f), AndroidMock.eq(0.0f)))) + .andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.and(AndroidMock.eq("hi"), AndroidMock.eq("hi")))) + .andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.and(AndroidMock.eq('a'), AndroidMock.eq('a')))) + .andReturn(null); + AndroidMock.replay(mockMap); + } + + public void testOr() { + Map<?, ?> mockMap = AndroidMock.createMock(Map.class); + AndroidMock.expect(mockMap.get(AndroidMock.or(AndroidMock.eq(true), AndroidMock.eq(true)))) + .andReturn(null); + AndroidMock.expect( + mockMap.get(AndroidMock.or(AndroidMock.eq((byte) 0), AndroidMock.eq((byte) 0)))) + .andReturn(null); + AndroidMock.expect( + mockMap.get(AndroidMock.or(AndroidMock.eq((short) 0), AndroidMock.eq((short) 0)))) + .andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.or(AndroidMock.eq(0), AndroidMock.eq(0)))) + .andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.or(AndroidMock.eq(0L), AndroidMock.eq(0L)))) + .andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.or(AndroidMock.eq(0.0), AndroidMock.eq(0.0)))) + .andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.or(AndroidMock.eq(0.0f), AndroidMock.eq(0.0f)))) + .andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.or(AndroidMock.eq("hi"), AndroidMock.eq("hi")))) + .andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.or(AndroidMock.eq('a'), AndroidMock.eq('a')))) + .andReturn(null); + AndroidMock.replay(mockMap); + } + + public void testNot() { + Map<?, ?> mockMap = AndroidMock.createMock(Map.class); + AndroidMock.expect(mockMap.get(AndroidMock.not(AndroidMock.eq(true)))).andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.not(AndroidMock.eq((byte) 0)))).andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.not(AndroidMock.eq((short) 0)))).andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.not(AndroidMock.eq(0)))).andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.not(AndroidMock.eq(0L)))).andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.not(AndroidMock.eq(0.0)))).andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.not(AndroidMock.eq(0.0f)))).andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.not(AndroidMock.eq("hi")))).andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.not(AndroidMock.eq('a')))).andReturn(null); + AndroidMock.replay(mockMap); + } + + public void testEq() { + Map<?, ?> mockMap = AndroidMock.createMock(Map.class); + AndroidMock.expect(mockMap.get(AndroidMock.eq(true))).andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.eq((byte) 0))).andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.eq((short) 0))).andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.eq(0))).andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.eq(0L))).andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.eq(0.0))).andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.eq(0.0f))).andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.eq(0.0, 0.1))).andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.eq(0.0f, 0.1f))).andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.eq("hi"))).andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.eq('a'))).andReturn(null); + AndroidMock.replay(mockMap); + } + + public void testAryEq() { + Map<?, ?> mockMap = AndroidMock.createMock(Map.class); + AndroidMock.expect(mockMap.get(AndroidMock.aryEq(new boolean[] {true}))).andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.aryEq(new byte[] {(byte) 0}))).andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.aryEq(new short[] {(short) 0}))).andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.aryEq(new int[] {0}))).andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.aryEq(new long[] {0L}))).andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.aryEq(new double[] {0.0}))).andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.aryEq(new float[] {0.0f}))).andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.aryEq(new String[] {"hi"}))).andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.aryEq(new char[] {'a'}))).andReturn(null); + AndroidMock.replay(mockMap); + } + + public void testIsNull() { + Map<?, ?> mockMap = AndroidMock.createMock(Map.class); + AndroidMock.expect(mockMap.get(AndroidMock.isNull())).andReturn(null); + AndroidMock.replay(mockMap); + } + + public void testNotNull() { + Map<?, ?> mockMap = AndroidMock.createMock(Map.class); + AndroidMock.expect(mockMap.get(AndroidMock.notNull())).andReturn(null); + AndroidMock.replay(mockMap); + } + + public void testFind() { + Map<?, ?> mockMap = AndroidMock.createMock(Map.class); + AndroidMock.expect(mockMap.get(AndroidMock.find("hi"))).andReturn(null); + AndroidMock.replay(mockMap); + } + + public void testMatches() { + Map<?, ?> mockMap = AndroidMock.createMock(Map.class); + AndroidMock.expect(mockMap.get(AndroidMock.matches("hi"))).andReturn(null); + AndroidMock.replay(mockMap); + } + + public void testStartsWith() { + Map<?, ?> mockMap = AndroidMock.createMock(Map.class); + AndroidMock.expect(mockMap.get(AndroidMock.startsWith("hi"))).andReturn(null); + AndroidMock.replay(mockMap); + } + + public void testEndsWith() { + Map<?, ?> mockMap = AndroidMock.createMock(Map.class); + AndroidMock.expect(mockMap.get(AndroidMock.endsWith("hi"))).andReturn(null); + AndroidMock.replay(mockMap); + } + + public void testSame() { + Map<?, ?> mockMap = AndroidMock.createMock(Map.class); + AndroidMock.expect(mockMap.get(AndroidMock.same("hi"))).andReturn(null); + AndroidMock.replay(mockMap); + } + + public void testCmpEq() { + Map<?, ?> mockMap = AndroidMock.createMock(Map.class); + AndroidMock.expect(mockMap.get(AndroidMock.cmpEq("hi"))).andReturn(null); + AndroidMock.replay(mockMap); + } + + public void testCmp() { + Map<?, ?> mockMap = AndroidMock.createMock(Map.class); + AndroidMock.expect( + mockMap.get(AndroidMock.cmp("hi", String.CASE_INSENSITIVE_ORDER, LogicalOperator.EQUAL))) + .andReturn(null); + AndroidMock.replay(mockMap); + } + + public void testCapture() { + Map<?, ?> mockMap = AndroidMock.createMock(Map.class); + AndroidMock.expect(mockMap.get(AndroidMock.capture(new Capture<Byte>()))).andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.capture(new Capture<Character>()))).andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.capture(new Capture<Double>()))).andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.capture(new Capture<Float>()))).andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.capture(new Capture<Integer>()))).andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.capture(new Capture<Long>()))).andReturn(null); + AndroidMock.expect(mockMap.get(AndroidMock.capture(new Capture<String>()))).andReturn(null); + AndroidMock.replay(mockMap); + } + + public void testReportMatcher() { + Map<?, ?> mockMap = AndroidMock.createMock(Map.class); + AndroidMock.reportMatcher(new Equals(null)); + AndroidMock.expect(mockMap.get(null)).andReturn(null); + AndroidMock.replay(mockMap); + } + + public void testGetCurrentArguments() { + Map<?, ?> mockMap = AndroidMock.createMock(Map.class); + AndroidMock.expect(mockMap.get(null)).andAnswer(new IAnswer() { + @Override + public Object answer() { + AndroidMock.getCurrentArguments(); + return null; + } + }); + AndroidMock.replay(mockMap); + mockMap.get(null); + } + + public void testMakeThreadSafe() { + Map<?, ?> mockMap = AndroidMock.createMock(Map.class); + AndroidMock.makeThreadSafe(mockMap, false); + AndroidMock.replay(mockMap); + } + + public void testAndThrowsOnMockedInterface() throws IOException { + ObjectInput mockInStream = AndroidMock.createMock(ObjectInput.class); + AndroidMock.expect(mockInStream.read()).andThrow(new IOException("foo")); + AndroidMock.replay(mockInStream); + try { + mockInStream.read(); + fail("IOException not thrown"); + } catch (IOException e) { + assertEquals("foo", e.getMessage()); + } + AndroidMock.verify(mockInStream); + } + + public void testAndThrowsOnMockedClass() throws IOException, ClassNotFoundException, + CannotCompileException, NotFoundException { + List<GeneratedClassFile> mockClasses = + new AndroidMockGenerator().createMocksForClass(InputStream.class); + compileClasses(mockClasses); + InputStream mockInStream = AndroidMock.createMock(InputStream.class); + AndroidMock.expect(mockInStream.read()).andThrow(new IOException("foo")); + AndroidMock.replay(mockInStream); + try { + mockInStream.read(); + fail("IOException not thrown"); + } catch (IOException e) { + assertEquals("foo", e.getMessage()); + } + AndroidMock.verify(mockInStream); + } + + /** + * Used for testing that a given method on Android Mock calls the equivalent + * method on EasyMock, to ensure that the method-wiring of Android Mock is + * correct. + * + * @author swoodward@google.com (Stephen Woodward) + */ + class MethodVerifier extends ExprEditor { + private CtMethod expectedMethod; + private boolean methodCalled; + + MethodVerifier(CtMethod expectedMethod) { + this.expectedMethod = expectedMethod; + } + + @Override + public void edit(MethodCall calledMethod) { + try { + methodCalled = methodCalled || expectedMethod.equals(calledMethod.getMethod()); + } catch (NotFoundException e) { + throw new RuntimeException(e); + } + } + + public boolean expectedMethodCalled() { + return methodCalled; + } + } +} diff --git a/tests/com/google/android/testing/mocking/ClassDoesWorkInConstructor.java b/tests/com/google/android/testing/mocking/ClassDoesWorkInConstructor.java new file mode 100644 index 0000000..a8c6a55 --- /dev/null +++ b/tests/com/google/android/testing/mocking/ClassDoesWorkInConstructor.java @@ -0,0 +1,77 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.testing.mocking; + + +/** + * Support class for testing. + * + * @author swoodward@google.com (Stephen Woodward) + */ +public class ClassDoesWorkInConstructor { + public ClassDoesWorkInConstructor() { + this.fooInt(1); + this.fooByte((byte) 1); + this.fooShort((short) 1); + this.fooChar('a'); + this.fooLong(1L); + this.fooFloat(1.0f); + this.fooDouble(1.0); + this.fooBoolean(true); + this.fooObject("hello"); + this.fooVoid(); + } + + public void fooVoid() { + throw new IllegalStateException("I wasn't mocked!!"); + } + + public Object fooObject(String string) { + throw new IllegalStateException("I wasn't mocked!!"); + } + + public boolean fooBoolean(boolean b) { + throw new IllegalStateException("I wasn't mocked!!"); + } + + public double fooDouble(double d) { + throw new IllegalStateException("I wasn't mocked!!"); + } + + public float fooFloat(float f) { + throw new IllegalStateException("I wasn't mocked!!"); + } + + public long fooLong(long i) { + throw new IllegalStateException("I wasn't mocked!!"); + } + + public char fooChar(char c) { + throw new IllegalStateException("I wasn't mocked!!"); + } + + public short fooShort(short s) { + throw new IllegalStateException("I wasn't mocked!!"); + } + + public byte fooByte(byte b) { + throw new IllegalStateException("I wasn't mocked!!"); + } + + public int fooInt(int i) { + throw new IllegalStateException("I wasn't mocked!!"); + } +} diff --git a/tests/com/google/android/testing/mocking/ClassHasDelegateMethods.java b/tests/com/google/android/testing/mocking/ClassHasDelegateMethods.java new file mode 100644 index 0000000..6745b91 --- /dev/null +++ b/tests/com/google/android/testing/mocking/ClassHasDelegateMethods.java @@ -0,0 +1,38 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.testing.mocking; + +/** + * Class with methods that match the delegate methods. Used only for testing. + * + * @author swoodward@google.com (Stephen Woodward) + */ +public class ClassHasDelegateMethods { + /** + * Test method. + * @param obj test obj. + */ + public void setDelegate___AndroidMock(Object obj) { + } + + /** + * Test method + * @return null + */ + public Object getDelegate___AndroidMock() { + return null; + } +} diff --git a/tests/com/google/android/testing/mocking/ClassHasFinalMethods.java b/tests/com/google/android/testing/mocking/ClassHasFinalMethods.java new file mode 100644 index 0000000..414f5f8 --- /dev/null +++ b/tests/com/google/android/testing/mocking/ClassHasFinalMethods.java @@ -0,0 +1,35 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.testing.mocking; + +/** + * Class with final methods. Used only for testing. + * + * @author swoodward@google.com (Stephen Woodward) + */ +public class ClassHasFinalMethods { + public final int foo() { + return 0; + } + + public int bar() { + return 0; + } + + public final int foobar() { + return 0; + } +} diff --git a/tests/com/google/android/testing/mocking/ClassHasNoDefaultConstructor.java b/tests/com/google/android/testing/mocking/ClassHasNoDefaultConstructor.java new file mode 100644 index 0000000..8b97cd8 --- /dev/null +++ b/tests/com/google/android/testing/mocking/ClassHasNoDefaultConstructor.java @@ -0,0 +1,31 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.testing.mocking; + +/** + * Class with no default constructor. Used only for testing. + * + * @author swoodward@google.com (Stephen Woodward) + */ +public class ClassHasNoDefaultConstructor { + public ClassHasNoDefaultConstructor(int foo) { + + } + + public int foo() { + return 0; + } +} diff --git a/tests/com/google/android/testing/mocking/ClassHasNoPublicConstructors.java b/tests/com/google/android/testing/mocking/ClassHasNoPublicConstructors.java new file mode 100644 index 0000000..20533ee --- /dev/null +++ b/tests/com/google/android/testing/mocking/ClassHasNoPublicConstructors.java @@ -0,0 +1,27 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.testing.mocking; + +/** + * Class with no public constructor. Used only for testing. + * + * @author swoodward@google.com (Stephen Woodward) + */ +public class ClassHasNoPublicConstructors { + private ClassHasNoPublicConstructors() { + + } +} diff --git a/tests/com/google/android/testing/mocking/ClassHasOverloadedMethods.java b/tests/com/google/android/testing/mocking/ClassHasOverloadedMethods.java new file mode 100644 index 0000000..f1b04fa --- /dev/null +++ b/tests/com/google/android/testing/mocking/ClassHasOverloadedMethods.java @@ -0,0 +1,30 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.testing.mocking; + +/** + * Class with overloaded methods. Used only for testing. + * + * @author swoodward@google.com (Stephen Woodward) + */ +public class ClassHasOverloadedMethods { + public int foo() { + return 0; + } + + public void foo(int arg) { + } +} diff --git a/tests/com/google/android/testing/mocking/ClassHasStaticMethods.java b/tests/com/google/android/testing/mocking/ClassHasStaticMethods.java new file mode 100644 index 0000000..29f4f52 --- /dev/null +++ b/tests/com/google/android/testing/mocking/ClassHasStaticMethods.java @@ -0,0 +1,31 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.testing.mocking; + +/** + * Class with static methods. Used only for testing. + * + * @author swoodward@google.com (Stephen Woodward) + */ +public class ClassHasStaticMethods { + public static int staticFoo() { + return 0; + } + + public int foo() { + return 1; + } +} diff --git a/tests/com/google/android/testing/mocking/ClassIsAnnotation.java b/tests/com/google/android/testing/mocking/ClassIsAnnotation.java new file mode 100644 index 0000000..e42c8e1 --- /dev/null +++ b/tests/com/google/android/testing/mocking/ClassIsAnnotation.java @@ -0,0 +1,25 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.testing.mocking; + +/** + * Annotation Class. Used only for testing. + * + * @author swoodward@google.com (Stephen Woodward) + */ +public @interface ClassIsAnnotation { + +} diff --git a/tests/com/google/android/testing/mocking/ClassIsEnum.java b/tests/com/google/android/testing/mocking/ClassIsEnum.java new file mode 100644 index 0000000..b7fa957 --- /dev/null +++ b/tests/com/google/android/testing/mocking/ClassIsEnum.java @@ -0,0 +1,25 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.testing.mocking; + +/** + * Enum Class. Used only for testing. + * + * @author swoodward@google.com (Stephen Woodward) + */ +public enum ClassIsEnum { + One, Two, Three, Four; +} diff --git a/tests/com/google/android/testing/mocking/ClassIsFinal.java b/tests/com/google/android/testing/mocking/ClassIsFinal.java new file mode 100644 index 0000000..88f8279 --- /dev/null +++ b/tests/com/google/android/testing/mocking/ClassIsFinal.java @@ -0,0 +1,27 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.testing.mocking; + +/** + * Final Class. Used only for testing. + * + * @author swoodward@google.com (Stephen Woodward) + */ +public final class ClassIsFinal { + public int foo() { + return 0; + } +} diff --git a/tests/com/google/android/testing/mocking/ClassIsInterface.java b/tests/com/google/android/testing/mocking/ClassIsInterface.java new file mode 100644 index 0000000..d48f512 --- /dev/null +++ b/tests/com/google/android/testing/mocking/ClassIsInterface.java @@ -0,0 +1,25 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.testing.mocking; + +/** + * Interface. Used only for testing. + * + * @author swoodward@google.com (Stephen Woodward) + */ +public interface ClassIsInterface { + int foo(); +} diff --git a/tests/com/google/android/testing/mocking/ClassTypeTests.java b/tests/com/google/android/testing/mocking/ClassTypeTests.java new file mode 100644 index 0000000..a4d60c2 --- /dev/null +++ b/tests/com/google/android/testing/mocking/ClassTypeTests.java @@ -0,0 +1,269 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.android.testing.mocking; + +import javassist.CannotCompileException; +import javassist.ClassPool; +import javassist.CtClass; +import javassist.CtMethod; +import javassist.NotFoundException; + +import junit.framework.TestCase; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + + +/** + * Various tests that verify that different types of Classes are handled + * correctly. + * + * @author swoodward@google.com (Stephen Woodward) + */ +public class ClassTypeTests extends TestCase { + private AndroidMockGenerator androidMockGenerator = new AndroidMockGenerator(); + + private AndroidMockGenerator getAndroidMockGenerator() { + return androidMockGenerator; + } + + private void assertAllMethodNames(List<String> expectedNames, + Map<String, List<String>> expectedMethods, List<GeneratedClassFile> classes) + throws IOException { + for (GeneratedClassFile clazz : classes) { + assertTrue(expectedNames.contains(clazz.getClassName())); + assertUnorderedContentsSame(expectedMethods.get(clazz.getClassName()), getMethodNames(clazz)); + } + } + + private <T> void assertUnorderedContentsSame(Iterable<T> expected, Iterable<T> actual) { + List<T> missingItems = new ArrayList<T>(); + List<T> extraItems = new ArrayList<T>(); + for (T item : expected) { + missingItems.add(item); + } + for (T item : actual) { + missingItems.remove(item); + extraItems.add(item); + } + for (T item : expected) { + extraItems.remove(item); + } + if (missingItems.size() + extraItems.size() != 0) { + String errorMessage = + "Contents were different. Missing: " + Arrays.toString(missingItems.toArray()) + + " Extra: " + Arrays.toString(extraItems.toArray()); + fail(errorMessage); + } + } + + private List<String> getExpectedNames(Class<?> clazz) { + return new ArrayList<String>(Arrays.asList(new String[] { + "genmocks." + clazz.getCanonicalName() + "DelegateInterface", + "genmocks." + clazz.getCanonicalName() + "DelegateSubclass"})); + } + + private Iterable<String> getMethodNames(GeneratedClassFile clazz) throws IOException { + ByteArrayInputStream classInputStream = new ByteArrayInputStream(clazz.getContents()); + CtClass ctClass; + try { + ctClass = ClassPool.getDefault().getCtClass(clazz.getClassName()); + if (ctClass.isFrozen()) { + ctClass.defrost(); + } + } catch (NotFoundException e) { + // That's ok, we're just defrosting any classes that affect us that were created + // by other tests. NotFoundException implies the class is not frozen. + } + ctClass = ClassPool.getDefault().makeClass(classInputStream); + return getMethodNames(ctClass.getDeclaredMethods()); + } + + private List<String> getMethodNames(CtMethod[] methods) { + List<String> methodNames = new ArrayList<String>(); + for (CtMethod method : methods) { + methodNames.add(method.getName()); + } + return methodNames; + } + + private List<String> getMethodNames(Method[] methods, String[] exclusions) { + List<String> methodNames = new ArrayList<String>(); + for (Method method : methods) { + if (!Arrays.asList(exclusions).contains(method.getName())) { + methodNames.add(method.getName()); + } + } + return methodNames; + } + + private Map<String, List<String>> getExpectedMethodsMap(List<String> expectedNames, + Class<?> clazz) { + return getExpectedMethodsMap(expectedNames, clazz, new String[0]); + } + + private Map<String, List<String>> getExpectedMethodsMap(List<String> expectedNames, + Class<?> clazz, String[] exclusions) { + Map<String, List<String>> expectedMethods = new HashMap<String, List<String>>(); + expectedMethods.put(expectedNames.get(0), new ArrayList<String>(Arrays.asList(new String[] { + "finalize", "clone"}))); + expectedMethods.put(expectedNames.get(1), new ArrayList<String>(Arrays.asList(new String[] { + "finalize", "clone", "setDelegate___AndroidMock", "getDelegate___AndroidMock"}))); + expectedMethods.get(expectedNames.get(0)).addAll( + getMethodNames(clazz.getDeclaredMethods(), exclusions)); + expectedMethods.get(expectedNames.get(1)).addAll( + getMethodNames(clazz.getDeclaredMethods(), exclusions)); + return expectedMethods; + } + + public void testClassIsDuplicate() throws ClassNotFoundException, IOException, + CannotCompileException { + List<GeneratedClassFile> classList = + getAndroidMockGenerator().createMocksForClass(Object.class); + List<GeneratedClassFile> secondClassList = + getAndroidMockGenerator().createMocksForClass(Object.class); + assertEquals(classList, secondClassList); + } + + public void testClassHasDelegateMethods() throws ClassNotFoundException, IOException, + CannotCompileException { + List<String> expectedNames = getExpectedNames(ClassHasDelegateMethods.class); + Map<String, List<String>> expectedMethods = + getExpectedMethodsMap(expectedNames, ClassHasDelegateMethods.class, + new String[] {"getDelegate___AndroidMock"}); + // This use case doesn't fit our util in any nice way, so just tweak it. + expectedMethods.get( + "genmocks.com.google.android.testing.mocking.ClassHasDelegateMethodsDelegateInterface") + .add("getDelegate___AndroidMock"); + + AndroidMockGenerator mockGenerator = getAndroidMockGenerator(); + List<GeneratedClassFile> classes = + mockGenerator.createMocksForClass(ClassHasDelegateMethods.class); + assertEquals(2, classes.size()); + assertAllMethodNames(expectedNames, expectedMethods, classes); + } + + public void testClassHasFinalMethods() throws ClassNotFoundException, IOException, + CannotCompileException { + List<String> expectedNames = getExpectedNames(ClassHasFinalMethods.class); + Map<String, List<String>> expectedMethods = + getExpectedMethodsMap(expectedNames, ClassHasFinalMethods.class, new String[] {"foo", + "foobar"}); + + AndroidMockGenerator mockGenerator = getAndroidMockGenerator(); + List<GeneratedClassFile> classes = + mockGenerator.createMocksForClass(ClassHasFinalMethods.class); + assertEquals(2, classes.size()); + assertAllMethodNames(expectedNames, expectedMethods, classes); + } + + public void testClassHasNoDefaultConstructor() throws ClassNotFoundException, IOException, + CannotCompileException { + AndroidMockGenerator mockGenerator = getAndroidMockGenerator(); + List<GeneratedClassFile> classes = + mockGenerator.createMocksForClass(ClassHasNoDefaultConstructor.class); + assertEquals(2, classes.size()); + } + + public void testClassHasNoPublicConstructors() throws ClassNotFoundException, IOException, + CannotCompileException { + AndroidMockGenerator mockGenerator = getAndroidMockGenerator(); + List<GeneratedClassFile> classes = + mockGenerator.createMocksForClass(ClassHasNoPublicConstructors.class); + assertEquals(0, classes.size()); + } + + public void testClassHasOverloadedMethods() throws ClassNotFoundException, IOException, + CannotCompileException { + List<String> expectedNames = getExpectedNames(ClassHasOverloadedMethods.class); + Map<String, List<String>> expectedMethods = + getExpectedMethodsMap(expectedNames, ClassHasOverloadedMethods.class); + + AndroidMockGenerator mockGenerator = getAndroidMockGenerator(); + List<GeneratedClassFile> classes = + mockGenerator.createMocksForClass(ClassHasOverloadedMethods.class); + assertEquals(2, classes.size()); + assertAllMethodNames(expectedNames, expectedMethods, classes); + } + + public void testClassHasStaticMethods() throws ClassNotFoundException, IOException, + CannotCompileException { + List<String> expectedNames = getExpectedNames(ClassHasStaticMethods.class); + Map<String, List<String>> expectedMethods = + getExpectedMethodsMap(expectedNames, ClassHasStaticMethods.class, + new String[] {"staticFoo"}); + + AndroidMockGenerator mockGenerator = getAndroidMockGenerator(); + List<GeneratedClassFile> classes = + mockGenerator.createMocksForClass(ClassHasStaticMethods.class); + assertEquals(2, classes.size()); + assertAllMethodNames(expectedNames, expectedMethods, classes); + } + + public void testClassIsAnnotation() throws ClassNotFoundException, IOException, + CannotCompileException { + AndroidMockGenerator mockGenerator = getAndroidMockGenerator(); + List<GeneratedClassFile> classes = mockGenerator.createMocksForClass(ClassIsAnnotation.class); + assertEquals(0, classes.size()); + } + + public void testClassIsEnum() throws ClassNotFoundException, IOException, CannotCompileException { + AndroidMockGenerator mockGenerator = getAndroidMockGenerator(); + List<GeneratedClassFile> classes = mockGenerator.createMocksForClass(ClassIsEnum.class); + assertEquals(0, classes.size()); + } + + public void testClassIsFinal() throws ClassNotFoundException, IOException, + CannotCompileException { + AndroidMockGenerator mockGenerator = getAndroidMockGenerator(); + List<GeneratedClassFile> classes = mockGenerator.createMocksForClass(ClassIsFinal.class); + assertEquals(0, classes.size()); + } + + public void testClassIsInterface() throws ClassNotFoundException, IOException, + CannotCompileException { + AndroidMockGenerator mockGenerator = getAndroidMockGenerator(); + List<GeneratedClassFile> classes = mockGenerator.createMocksForClass(ClassIsInterface.class); + assertEquals(0, classes.size()); + } + + public void testClassIsArray() throws ClassNotFoundException, IOException, + CannotCompileException { + AndroidMockGenerator mockGenerator = getAndroidMockGenerator(); + List<GeneratedClassFile> classes = mockGenerator.createMocksForClass(Object[].class); + assertEquals(0, classes.size()); + } + + public void testClassIsNormal() throws ClassNotFoundException, IOException, + CannotCompileException { + AndroidMockGenerator mockGenerator = getAndroidMockGenerator(); + List<GeneratedClassFile> classes = mockGenerator.createMocksForClass(Object.class); + assertEquals(2, classes.size()); + } + + public void testClassIsPrimitive() throws ClassNotFoundException, IOException, + CannotCompileException { + AndroidMockGenerator mockGenerator = getAndroidMockGenerator(); + List<GeneratedClassFile> classes = mockGenerator.createMocksForClass(Integer.TYPE); + assertEquals(0, classes.size()); + } +} diff --git a/tests/com/google/android/testing/mocking/ConstructorCreationTests.java b/tests/com/google/android/testing/mocking/ConstructorCreationTests.java new file mode 100644 index 0000000..152918d --- /dev/null +++ b/tests/com/google/android/testing/mocking/ConstructorCreationTests.java @@ -0,0 +1,131 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.testing.mocking; + +import junit.framework.TestCase; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Type; + + +/** + * Tests for mocked objects with non default constructors. + * + * @author swoodward@google.com (Stephen Woodward) + */ +public class ConstructorCreationTests extends TestCase { + public static class Foo { + private int value; + Foo(int value) { this.value = value; } + int get() { return value; } + } + + public static class Bar { + private double value; + Bar(double value) { this.value = value; } + double get() { return value; } + } + + public static class TestClass { + public int v1; + public double v2; + public boolean usedFloatConstructor; + + public TestClass(Foo foo) { + this(foo.get()); + } + + public TestClass(Foo foo, Bar bar) { + this(foo.get(), bar.get()); + } + + public TestClass(int v1) { + this(v1, 0); + } + + public TestClass(int v1, float v2) { + this.v1 = v1; + this.v2 = v2; + usedFloatConstructor = true; + } + + public TestClass(int v1, double v2) { + this.v1 = v1; + this.v2 = (int) v2; + usedFloatConstructor = false; + } + } + + private void hasConstructor(Object... args) { + Constructor<TestClass> constructor = + AndroidMock.getConstructorFor(TestClass.class, args); + assertNotNull(constructor); + } + + private void doesNotHaveConstructor(Object... args) { + try { + Constructor<TestClass> constructor = + AndroidMock.getConstructorFor(TestClass.class, args); + fail("A constructor was found: " + constructor); + } catch (IllegalArgumentException e) { + // expected + } + } + + public void testConstructors() { + hasConstructor(new Foo(1)); + doesNotHaveConstructor(new Bar(2)); + hasConstructor(new Foo(1), new Bar(2)); + hasConstructor(1); + hasConstructor(1, 2); + doesNotHaveConstructor(new Foo(1), 2); + hasConstructor(1, new Integer("2")); + hasConstructor(1, 2.0); + hasConstructor(1, 2.0f); + } + + private void checkConstructor(Object[] args, Type[] expectedTypes) { + Constructor<TestClass> constructor = + AndroidMock.getConstructorFor(TestClass.class, args); + assertNotNull(constructor); + Type[] types = constructor.getGenericParameterTypes(); + assertEquals(expectedTypes.length, types.length); + for (int i = 0; i < expectedTypes.length; ++i) { + assertEquals(expectedTypes[i], types[i]); + } + } + + public void testCorrectConstructor() { + checkConstructor( + new Object[]{new Foo(1)}, + new Type[]{Foo.class}); + checkConstructor( + new Object[]{new Foo(1), new Bar(2)}, + new Type[]{Foo.class, Bar.class}); + checkConstructor( + new Object[]{1}, + new Type[]{Integer.TYPE}); + checkConstructor( + new Object[]{1, new Float("2")}, + new Type[]{Integer.TYPE, Float.TYPE}); + checkConstructor( + new Object[]{1, 2.0}, + new Type[]{Integer.TYPE, Double.TYPE}); + checkConstructor( + new Object[]{1, 2.0f}, + new Type[]{Integer.TYPE, Float.TYPE}); + } +}
\ No newline at end of file diff --git a/tests/com/google/android/testing/mocking/FileUtilsTest.java b/tests/com/google/android/testing/mocking/FileUtilsTest.java new file mode 100644 index 0000000..b1058e3 --- /dev/null +++ b/tests/com/google/android/testing/mocking/FileUtilsTest.java @@ -0,0 +1,70 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.testing.mocking; + +import junit.framework.TestCase; + +import java.util.Vector; + +/** + * @author swoodward@google.com (Stephen Woodward) + */ +public class FileUtilsTest extends TestCase { + public void testGetFilenameForClass() { + assertEquals("java/lang/Object.class", FileUtils.getFilenameFor(Object.class.getName())); + assertEquals("com/google/android/testing/mocking/FileUtilsTest$InnerClass.class", + FileUtils.getFilenameFor(InnerClass.class.getName())); + } + + public void testGetClassNameFor() { + assertEquals("java/lang/Object.class", FileUtils.getFilenameFor(Object.class.getName())); + assertEquals("com/google/android/testing/mocking/FileUtilsTest$InnerClass.class", + FileUtils.getFilenameFor(InnerClass.class.getName())); + } + + public void testGetInterfaceNameFor() { + assertEquals("v15.genmocks.java.util.VectorDelegateInterface", + FileUtils.getInterfaceNameFor(Vector.class, SdkVersion.CUPCAKE)); + assertEquals("v16.genmocks.java.util.VectorDelegateInterface", + FileUtils.getInterfaceNameFor(Vector.class, SdkVersion.DONUT)); + assertEquals("v201.genmocks.java.util.VectorDelegateInterface", + FileUtils.getInterfaceNameFor(Vector.class, SdkVersion.ECLAIR_0_1)); + assertEquals("v21.genmocks.java.util.VectorDelegateInterface", + FileUtils.getInterfaceNameFor(Vector.class, SdkVersion.ECLAIR_MR1)); + assertEquals("v22.genmocks.java.util.VectorDelegateInterface", + FileUtils.getInterfaceNameFor(Vector.class, SdkVersion.FROYO)); + assertEquals("genmocks.java.util.VectorDelegateInterface", + FileUtils.getInterfaceNameFor(Vector.class, SdkVersion.UNKNOWN)); + } + + public void testGetSubclassNameFor() { + assertEquals("v15.genmocks.java.util.VectorDelegateSubclass", + FileUtils.getSubclassNameFor(Vector.class, SdkVersion.CUPCAKE)); + assertEquals("v16.genmocks.java.util.VectorDelegateSubclass", + FileUtils.getSubclassNameFor(Vector.class, SdkVersion.DONUT)); + assertEquals("v201.genmocks.java.util.VectorDelegateSubclass", + FileUtils.getSubclassNameFor(Vector.class, SdkVersion.ECLAIR_0_1)); + assertEquals("v21.genmocks.java.util.VectorDelegateSubclass", + FileUtils.getSubclassNameFor(Vector.class, SdkVersion.ECLAIR_MR1)); + assertEquals("v22.genmocks.java.util.VectorDelegateSubclass", + FileUtils.getSubclassNameFor(Vector.class, SdkVersion.FROYO)); + assertEquals("genmocks.java.util.VectorDelegateSubclass", + FileUtils.getSubclassNameFor(Vector.class, SdkVersion.UNKNOWN)); + } + + class InnerClass { + } +} diff --git a/tests/com/google/android/testing/mocking/SdkVersionTest.java b/tests/com/google/android/testing/mocking/SdkVersionTest.java new file mode 100644 index 0000000..eb7c764 --- /dev/null +++ b/tests/com/google/android/testing/mocking/SdkVersionTest.java @@ -0,0 +1,75 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.testing.mocking; + +import junit.framework.TestCase; + +import java.util.Arrays; +import java.util.List; + +/** + * @author swoodward@google.com (Stephen Woodward) + */ +public class SdkVersionTest extends TestCase { + + public void testGetAllVersions() { + List<SdkVersion> versions = Arrays.asList(SdkVersion.getAllVersions()); + List<SdkVersion> values = Arrays.asList(SdkVersion.values()); + assertEquals(5, versions.size()); + assertTrue(values.containsAll(versions)); + assertFalse(versions.contains(SdkVersion.UNKNOWN)); + } + + public void testGetVersionName() { + assertEquals("v15", SdkVersion.CUPCAKE.getVersionName()); + assertEquals("v16", SdkVersion.DONUT.getVersionName()); + assertEquals("v201", SdkVersion.ECLAIR_0_1.getVersionName()); + assertEquals("v21", SdkVersion.ECLAIR_MR1.getVersionName()); + assertEquals("v22", SdkVersion.FROYO.getVersionName()); + assertEquals("", SdkVersion.UNKNOWN.getVersionName()); + assertEquals("Unknown new SDK has been added, update this test", + 6, SdkVersion.values().length); + } + + public void testGetPackagePrefix() { + assertEquals("v15.", SdkVersion.CUPCAKE.getPackagePrefix()); + assertEquals("v16.", SdkVersion.DONUT.getPackagePrefix()); + assertEquals("v201.", SdkVersion.ECLAIR_0_1.getPackagePrefix()); + assertEquals("v21.", SdkVersion.ECLAIR_MR1.getPackagePrefix()); + assertEquals("v22.", SdkVersion.FROYO.getPackagePrefix()); + assertEquals("", SdkVersion.UNKNOWN.getPackagePrefix()); + assertEquals("Unknown new SDK has been added, update this test", + 6, SdkVersion.values().length); + } + + public void testGetCurrentVersion() { + // Always UNKNOWN on the desktop + assertEquals(SdkVersion.UNKNOWN, SdkVersion.getCurrentVersion()); + } + + public void testGetVersionFor() { + assertEquals(SdkVersion.CUPCAKE, SdkVersion.getVersionFor(3)); + assertEquals(SdkVersion.DONUT, SdkVersion.getVersionFor(4)); + assertEquals(SdkVersion.ECLAIR_0_1, SdkVersion.getVersionFor(6)); + assertEquals(SdkVersion.ECLAIR_MR1, SdkVersion.getVersionFor(7)); + assertEquals(SdkVersion.FROYO, SdkVersion.getVersionFor(8)); + assertEquals(SdkVersion.UNKNOWN, SdkVersion.getVersionFor(-1)); + for (int i = 9; i < 50; ++i) { + assertEquals("Unknown new SDK has been added, update this test", + SdkVersion.UNKNOWN, SdkVersion.getVersionFor(i)); + } + } +} diff --git a/tests/com/google/android/testing/mocking/UsesMocksProcessorTest.java b/tests/com/google/android/testing/mocking/UsesMocksProcessorTest.java new file mode 100644 index 0000000..0b8961b --- /dev/null +++ b/tests/com/google/android/testing/mocking/UsesMocksProcessorTest.java @@ -0,0 +1,275 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.android.testing.mocking; + +import javassist.CannotCompileException; + +import junit.framework.TestCase; + +import org.easymock.EasyMock; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.annotation.processing.Filer; +import javax.annotation.processing.Messager; +import javax.annotation.processing.ProcessingEnvironment; +import javax.annotation.processing.RoundEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.TypeVisitor; +import javax.tools.JavaFileObject; + +/** + * @author swoodward@google.com (Stephen Woodward) + */ +public class UsesMocksProcessorTest extends TestCase { + + private Set<? extends Element> getAnnotatedElementsSet(Class<?>... classes) { + Set<Element> set = new HashSet<Element>(); + for (Class<?> clazz : classes) { + set.add(getMockElement(clazz)); + } + return set; + } + + @SuppressWarnings("unchecked") + private Element getMockElement(Class<?> clazz) { + Element mockElement = EasyMock.createNiceMock(Element.class); + EasyMock.expect(mockElement.getAnnotationMirrors()).andReturn(getMockAnnotationMirrors(clazz)) + .anyTimes(); + EasyMock.replay(mockElement); + return mockElement; + } + + @SuppressWarnings("unchecked") + private List getMockAnnotationMirrors(Class<?> clazz) { + List<AnnotationMirror> mockMirrorList = new ArrayList<AnnotationMirror>(); + AnnotationMirror mockMirror = EasyMock.createNiceMock(AnnotationMirror.class); + EasyMock.expect(mockMirror.getAnnotationType()).andReturn(getMockAnnotationType()).anyTimes(); + EasyMock.expect(mockMirror.getElementValues()).andReturn(getMockElementValuesMap(clazz)) + .anyTimes(); + EasyMock.replay(mockMirror); + mockMirrorList.add(mockMirror); + return mockMirrorList; + } + + @SuppressWarnings("unchecked") + private Map getMockElementValuesMap(Class<?> clazz) { + Map mockValuesMap = new HashMap(); + mockValuesMap.put(getMockExecutableElement(), getMockAnnotationValue(clazz)); + return mockValuesMap; + } + + private AnnotationValue getMockAnnotationValue(Class<?> clazz) { + AnnotationValue mockValue = EasyMock.createMock(AnnotationValue.class); + EasyMock.expect(mockValue.getValue()).andReturn( + Arrays.asList(new String[] {clazz.getName() + ".class"})).anyTimes(); + EasyMock.replay(mockValue); + return mockValue; + } + + private ExecutableElement getMockExecutableElement() { + ExecutableElement mockElement = EasyMock.createNiceMock(ExecutableElement.class); + EasyMock.replay(mockElement); + return mockElement; + } + + private DeclaredType getMockAnnotationType() { + return new DeclaredType() { + @Override + public String toString() { + return UsesMocks.class.getName(); + } + + @Override + public Element asElement() { + return null; + } + + @Override + public TypeMirror getEnclosingType() { + return null; + } + + @Override + public List<? extends TypeMirror> getTypeArguments() { + return null; + } + + @Override + public <R, P> R accept(TypeVisitor<R, P> v, P p) { + return null; + } + + @Override + public TypeKind getKind() { + return null; + } + }; + } + + private UsesMocksProcessor getProcessor() { + return getProcessor(getMockProcessingEnvironment()); + } + + private UsesMocksProcessor getProcessor(ProcessingEnvironment processingEnv) { + UsesMocksProcessor processor = new UsesMocksProcessor(); + processor.init(processingEnv); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + processor.logger = new ProcessorLogger(outputStream, processingEnv); + return processor; + } + + private ProcessingEnvironment getMockProcessingEnvironment(Filer mockFiler) { + ProcessingEnvironment mockEnvironment = EasyMock.createNiceMock(ProcessingEnvironment.class); + EasyMock.expect(mockEnvironment.getMessager()).andReturn(getMockMessager()).anyTimes(); + EasyMock.expect(mockEnvironment.getFiler()).andReturn(mockFiler).anyTimes(); + EasyMock.expect(mockEnvironment.getOptions()).andReturn(getMockOptions()).anyTimes(); + EasyMock.replay(mockEnvironment); + return mockEnvironment; + } + + private Map<String, String> getMockOptions() { + Map<String, String> map = new HashMap<String, String>(); + map.put("bin_dir", "."); + map.put("logfile", "logfile"); + return map; + } + + private ProcessingEnvironment getMockProcessingEnvironment() { + return getMockProcessingEnvironment(getMockFiler()); + } + + private Messager getMockMessager() { + Messager mockMessager = EasyMock.createNiceMock(Messager.class); + EasyMock.replay(mockMessager); + return mockMessager; + } + + private Filer getMockFiler() { + try { + return getMockFiler(getMockFileObject()); + } catch (IOException e) { + // Can't happen + throw new RuntimeException(e); + } + } + + private Filer getMockFiler(JavaFileObject mockFileObject) { + Filer mockFiler = EasyMock.createNiceMock(Filer.class); + try { + EasyMock.expect(mockFiler.createClassFile((CharSequence) EasyMock.anyObject())).andReturn( + mockFileObject).anyTimes(); + } catch (IOException e) { + // Can't happen + throw new RuntimeException(e); + } + EasyMock.replay(mockFiler); + return mockFiler; + } + + private JavaFileObject getMockFileObject() throws IOException { + return getMockFileObject(new ByteArrayOutputStream()); + } + + private JavaFileObject getMockFileObject(OutputStream outStream) throws IOException { + JavaFileObject mockFileObject = EasyMock.createNiceMock(JavaFileObject.class); + EasyMock.expect(mockFileObject.openOutputStream()).andReturn(outStream).anyTimes(); + EasyMock.replay(mockFileObject); + return mockFileObject; + } + + private RoundEnvironment getMockRoundEnvironment(Set<? extends Element> elementsWithAnnotation) { + return getMockRoundEnvironment(elementsWithAnnotation, false); + } + + @SuppressWarnings("unchecked") + private RoundEnvironment getMockRoundEnvironment(Set<? extends Element> elementsWithAnnotation, + boolean finishedProcessing) { + RoundEnvironment mockEnv = EasyMock.createNiceMock(RoundEnvironment.class); + EasyMock.expect(mockEnv.getElementsAnnotatedWith(UsesMocks.class)).andReturn( + (Set) elementsWithAnnotation).anyTimes(); + EasyMock.expect(mockEnv.processingOver()).andReturn(finishedProcessing).anyTimes(); + EasyMock.replay(mockEnv); + return mockEnv; + } + + public void testGetClassMocks() throws IOException, CannotCompileException { + List<Class<?>> classesToMock = new ArrayList<Class<?>>(); + classesToMock.add(TestCase.class); + List<String> expectedMocks = + new ArrayList<String>(Arrays.asList(new String[] { + "genmocks." + TestCase.class.getName() + "DelegateInterface", + "genmocks." + TestCase.class.getName() + "DelegateSubclass"})); + Set<GeneratedClassFile> mockedClasses = + getProcessor().getClassMocks(classesToMock, true); + + assertEquals(2, mockedClasses.size()); + for (GeneratedClassFile clazz : mockedClasses) { + assertTrue(expectedMocks.contains(clazz.getClassName())); + expectedMocks.remove(clazz.getClassName()); + } + } + + public void testWriteMocks() throws IOException, CannotCompileException { + List<Class<?>> classesToMock = new ArrayList<Class<?>>(); + classesToMock.add(TestCase.class); + Set<GeneratedClassFile> mockedClassesSet = + getProcessor().getClassMocks(classesToMock, true); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + + getProcessor(getMockProcessingEnvironment(getMockFiler(getMockFileObject(outputStream)))) + .writeMocks(mockedClassesSet); + + String output = new String(outputStream.toByteArray()); + for (GeneratedClassFile mockClass : mockedClassesSet) { + String expected = new String(mockClass.getContents()); + assertTrue(output.contains(expected)); + output = output.replace(expected, ""); + } + assertEquals(0, output.length()); + } + + public void testProcess() { + assertFalse(getProcessor().process(null, + getMockRoundEnvironment(getAnnotatedElementsSet(TestCase.class)))); + assertFalse(getProcessor().process(null, + getMockRoundEnvironment(getAnnotatedElementsSet(TestCase.class), true))); + } + + public void testFindClassesToMock() { + Set<? extends Element> annotatedElements = getAnnotatedElementsSet(Set.class, TestCase.class); + List<Class<?>> classesList = getProcessor().findClassesToMock(annotatedElements); + + assertEquals(annotatedElements.size(), classesList.size()); + assertTrue(classesList.contains(Set.class)); + assertTrue(classesList.contains(TestCase.class)); + } +} |