├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── assets
│ └── mock_json
│ │ ├── picsum_favorites.json
│ │ └── picsum_list.json
│ ├── java
│ └── ir
│ │ └── logicbase
│ │ └── mockfit
│ │ └── app
│ │ ├── Api.kt
│ │ ├── AppExecutors.kt
│ │ ├── MainActivity.kt
│ │ ├── MyApplication.kt
│ │ ├── Picsum.kt
│ │ └── RemoteDataSource.kt
│ └── res
│ ├── drawable-v24
│ └── ic_launcher_foreground.xml
│ ├── drawable
│ └── ic_launcher_background.xml
│ ├── layout
│ └── activity_main.xml
│ ├── mipmap-anydpi-v26
│ ├── ic_launcher.xml
│ └── ic_launcher_round.xml
│ ├── mipmap-hdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-mdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ └── values
│ ├── colors.xml
│ ├── dimens.xml
│ ├── strings.xml
│ └── themes.xml
├── build.gradle
├── common
├── .gitignore
├── build.gradle
└── src
│ └── main
│ └── java
│ └── ir
│ └── logicbase
│ └── mockfit
│ ├── Mock.kt
│ ├── MockPathRule.kt
│ └── StringExtensions.kt
├── compiler
├── .gitignore
├── build.gradle
└── src
│ └── main
│ └── java
│ └── ir
│ └── logicbase
│ └── mockfit
│ └── MockFitProcessor.kt
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── runtime
├── .gitignore
├── build.gradle
└── src
│ └── main
│ └── java
│ └── ir
│ └── logicbase
│ └── mockfit
│ ├── BodyFactory.kt
│ ├── Logger.kt
│ └── MockFitInterceptor.kt
├── settings.gradle
└── versions.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by https://www.gitignore.io/api/androidstudio
2 |
3 | ### AndroidStudio ###
4 | # Covers files to be ignored for android development using Android Studio.
5 |
6 | # Built application files
7 | *.apk
8 | *.ap_
9 |
10 | # Files for the ART/Dalvik VM
11 | *.dex
12 |
13 | # Java class files
14 | *.class
15 |
16 | # Generated files
17 | bin/
18 | gen/
19 | out/
20 |
21 | # Gradle files
22 | .gradle
23 | .gradle/
24 | build/
25 |
26 | # Signing files
27 | .signing/
28 |
29 | # Local configuration file (sdk path, etc)
30 | local.properties
31 |
32 | # Proguard folder generated by Eclipse
33 | proguard/
34 |
35 | # Log Files
36 | *.log
37 |
38 | # Android Studio
39 | /*/build/
40 | /*/local.properties
41 | /*/out
42 | /*/*/build
43 | /*/*/production
44 | captures/
45 | .navigation/
46 | *.ipr
47 | *~
48 | *.swp
49 |
50 | # Android Patch
51 | gen-external-apklibs
52 |
53 | # External native build folder generated in Android Studio 2.2 and later
54 | .externalNativeBuild
55 |
56 | # NDK
57 | obj/
58 |
59 | ### Windows ###
60 |
61 | # Folder config file
62 | Desktop.ini
63 |
64 | # Recycle Bin used on file shares
65 | $RECYCLE.BIN/
66 |
67 | # Windows Installer files
68 | *.cab
69 | *.msi
70 | *.msm
71 | *.msp
72 |
73 | # Windows shortcuts
74 | *.lnk
75 |
76 | ### Linux ###
77 | *~
78 |
79 | # KDE directory preferences
80 | .directory
81 |
82 | # Linux trash folder which might appear on any partition or disk
83 | .Trash-*
84 |
85 | # IntelliJ IDEA
86 | *.iml
87 | *.iws
88 | /out/
89 |
90 | # User-specific configurations
91 | .idea/
92 | # if you remove the above rule, at least ignore the following:
93 | #.idea/libraries/
94 | #.idea/workspace.xml
95 | #.idea/tasks.xml
96 | #.idea/.name
97 | #.idea/compiler.xml
98 | #.idea/copyright/profiles_settings.xml
99 | #.idea/encodings.xml
100 | #.idea/misc.xml
101 | #.idea/modules.xml
102 | #.idea/scopes/scope_settings.xml
103 | #.idea/dictionaries
104 | #.idea/vcs.xml
105 | #.idea/jsLibraryMappings.xml
106 | #.idea/datasources.xml
107 | #.idea/dataSources.ids
108 | #.idea/sqlDataSources.xml
109 | #.idea/dynamic.xml
110 | #.idea/uiDesigner.xml
111 |
112 | # OS-specific files
113 | .DS_Store
114 | .DS_Store?
115 | ._*
116 | .Spotlight-V100
117 | .Trashes
118 | ehthumbs.db
119 | Thumbs.db
120 |
121 | # Legacy Eclipse project files
122 | .classpath
123 | .project
124 |
125 | # Mobile Tools for Java (J2ME)
126 | .mtj.tmp/
127 |
128 | # Package Files #
129 | *.jar
130 | *.war
131 | *.ear
132 |
133 | # virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml)
134 | hs_err_pid*
135 |
136 | ## Plugin-specific files:
137 |
138 | # mpeltonen/sbt-idea plugin
139 | .idea_modules/
140 |
141 | # JIRA plugin
142 | atlassian-ide-plugin.xml
143 |
144 | # Mongo Explorer plugin
145 | .idea/mongoSettings.xml
146 |
147 | # Keystore
148 | keystore.jks
149 | keystore.properties
150 | debug.keystore
151 | .jks
152 |
153 | # env
154 | env.properties
155 |
156 | # dependency graph generator
157 | dig/
158 |
159 | # Crashlytics plugin (for Android Studio and IntelliJ)
160 | com_crashlytics_export_strings.xml
161 | crashlytics.properties
162 | crashlytics-build.properties
163 | fabric.properties
164 |
165 | ### AndroidStudio Patch ###
166 |
167 | !/gradle/wrapper/gradle-wrapper.jar
168 |
169 | # End of https://www.gitignore.io/api/androidstudio
170 | true/values/com_crashlytics_build_id.xml
171 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # 2.0.0
2 | - Support for include and exclude json path based on query params (Thanks to @[Husseinhj](https://github.com/Husseinhj))
3 |
4 | # 1.1.0
5 | - Escape closing curly brace in regex to support in android
6 | - Add support to retrofit HTTP annotation
7 | - Add jitpack dependency to common module
8 |
9 | # 1.0.0
10 | - First stable release
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MockFit
2 | [](https://jitpack.io/#javaherisaber/MockFit)
3 | [](https://android-arsenal.com/details/1/8462)
4 |
5 | Kotlin library to intercept [Retrofit](https://github.com/square/retrofit) requests and provide a static JSON instead of remote response
6 |
7 |
8 |
9 | ## Dependency
10 | Top level build.gradle
11 | ```groovy
12 | allprojects {
13 | repositories {
14 | ...
15 | maven { url 'https://jitpack.io' }
16 | }
17 | }
18 | ```
19 |
20 | Module level build.gradle
21 | ```groovy
22 | dependencies {
23 | implementation "com.github.javaherisaber.MockFit:runtime:$versions.mockFit"
24 | kapt "com.github.javaherisaber.MockFit:compiler:$versions.mockFit" // for Kotlin (make sure to include kapt plugin also)
25 | annotationProcessor "com.github.javaherisaber.MockFit:compiler:$versions.mockFit" // for Java
26 | }
27 | ```
28 |
29 | ## How to use
30 |
31 | 1. Create json file of your api response lets say `picsum_list.json` and put it in `assets/mock_json` directory
32 |
33 | ```json
34 | [
35 | {
36 | "id": "1016",
37 | "author": "Andrew Ridley",
38 | "width": 3844,
39 | "height": 2563,
40 | "url": "https://unsplash.com/photos/_h7aBovKia4",
41 | "download_url": "https://picsum.photos/id/1018/3914/2935"
42 | },
43 | {
44 | "id": "1018",
45 | "author": "Andrew Ridley",
46 | "width": 3914,
47 | "height": 2935,
48 | "url": "https://unsplash.com/photos/Kt5hRENuotI",
49 | "download_url": "https://picsum.photos/id/1018/3914/2935"
50 | },
51 | {
52 | "id": "1019",
53 | "author": "Patrick Fore",
54 | "width": 5472,
55 | "height": 3648,
56 | "url": "https://unsplash.com/photos/V6s1cmE39XM",
57 | "download_url": "https://picsum.photos/id/1019/5472/3648"
58 | }
59 | ]
60 | ```
61 |
62 | 2. Define your api interface and annotate endpoint with `@mock("picsum_list.json")` so that Mockfit can generate config for you
63 | ```kotlin
64 | interface Api {
65 |
66 | @Mock("picsum_list.json")
67 | @GET("list")
68 | fun getListOfPicsums(
69 | @Query("page") page: Int,
70 | @Query("limit") limit: Int
71 | ): Call>
72 |
73 | // if you want to filter through query params
74 | @Mock("picsum_recent.json", excludeQueries = ["type=favorites"])
75 | @GET("list")
76 | fun getRecentPicsums(
77 | @Query("type") type: String,
78 | @Query("page") page: Int,
79 | @Query("limit") limit: Int
80 | ): Call>
81 |
82 | // if you want to filter through query params
83 | @Mock("picsum_favorites.json", includeQueries = ["type=favorites"])
84 | @GET("list")
85 | fun getFavoritePicsums(
86 | @Query("type") type: String,
87 | @Query("page") page: Int,
88 | @Query("limit") limit: Int
89 | ): Call>
90 | }
91 | ```
92 |
93 | 3. Add `MockfitInterceptor` to retrofit configuration
94 | ```kotlin
95 | class RemoteDataSource(private val context: Context) {
96 |
97 | fun api(): Api {
98 | val mockFitInterceptor = provideMockFitInterceptor(context)
99 | val okHttpClient = provideOkHttpClient(mockFitInterceptor)
100 | val retrofit = provideRetrofit(okHttpClient)
101 | return retrofit.create(Api::class.java)
102 | }
103 |
104 | private fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit = Retrofit.Builder()
105 | .baseUrl(BASE_URL)
106 | .addConverterFactory(GsonConverterFactory.create())
107 | .client(okHttpClient)
108 | .build()
109 |
110 | private fun provideMockFitInterceptor(context: Context) = MockFitInterceptor(
111 | bodyFactory = { input -> context.resources.assets.open(input) }, // read asset file
112 | logger = { tag, message -> Log.d(tag, message) }, // pass logger to log events in logcat
113 | baseUrl = BASE_URL, // base url of your api
114 | requestPathToMockPathRule = REQUEST_TO_JSON, // autogenerated constant, just press build button
115 | mockFilesPath = MOCK_FILES_PATH, // path to json files
116 | mockFitEnable = true, // master setting to enable or disable mocking
117 | apiEnableMock = true, // enable or disable mock when there are includes and excludes configs
118 | apiIncludeIntoMock = arrayOf(), // include endpoint if `apiEnableMock` is false
119 | apiExcludeFromMock = arrayOf(), // exclude endpoint if `apiEnableMock` is true
120 | apiResponseLatency = 500L // latency of retrieving data
121 | )
122 |
123 | private fun provideOkHttpClient(mockFitInterceptor: MockFitInterceptor) = OkHttpClient.Builder()
124 | .addInterceptor(mockFitInterceptor)
125 | .connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS)
126 | .writeTimeout(WRITE_TIMEOUT, TimeUnit.SECONDS)
127 | .readTimeout(READ_TIMEOUT, TimeUnit.SECONDS)
128 | .build()
129 |
130 | companion object {
131 | private const val BASE_URL = "https://picsum.photos/v2/"
132 | private const val MOCK_FILES_PATH = "mock_json" // located at assets/mock_json/
133 | private const val CONNECT_TIMEOUT = 20L
134 | private const val WRITE_TIMEOUT = 20L
135 | private const val READ_TIMEOUT = 30L
136 | }
137 | }
138 | ```
139 |
140 | 4. Rebuild (or just build current module)
141 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application'
3 | id 'kotlin-android-extensions'
4 | id 'kotlin-android'
5 | id 'kotlin-kapt'
6 | }
7 |
8 | android {
9 | compileSdkVersion config.compileSdk
10 | defaultConfig {
11 | applicationId "ir.logicbase.mockfit.app"
12 | minSdkVersion config.minSdk
13 | targetSdkVersion config.targetSdk
14 | versionCode 1
15 | versionName "1.0"
16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
17 | multiDexEnabled true
18 | }
19 |
20 | buildTypes {
21 | release {
22 | minifyEnabled false
23 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
24 | }
25 | }
26 | compileOptions {
27 | sourceCompatibility JavaVersion.VERSION_1_8
28 | targetCompatibility JavaVersion.VERSION_1_8
29 | }
30 | kotlinOptions {
31 | jvmTarget = '1.8'
32 | }
33 | }
34 |
35 | dependencies {
36 | implementation "com.google.android.material:material:$versions.material"
37 | implementation "androidx.multidex:multidex:$versions.multidex"
38 | implementation "androidx.constraintlayout:constraintlayout:$versions.constraintLayout"
39 | implementation "com.squareup.retrofit2:retrofit:$versions.retrofit"
40 | implementation "com.squareup.retrofit2:converter-gson:$versions.retrofit"
41 | implementation "com.github.bumptech.glide:glide:$versions.glide"
42 |
43 | testImplementation "junit:junit:$versions.junit"
44 | androidTestImplementation "androidx.test.espresso:espresso-core:$versions.espresso"
45 |
46 | kapt project(':compiler')
47 | implementation project(':runtime')
48 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
18 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/app/src/main/assets/mock_json/picsum_favorites.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "id": "1016",
4 | "author": "Andrew Ridley",
5 | "width": 3844,
6 | "height": 2563,
7 | "url": "https://unsplash.com/photos/_h7aBovKia4",
8 | "download_url": "https://picsum.photos/id/1018/3914/2935"
9 | },
10 | {
11 | "id": "1018",
12 | "author": "Andrew Ridley",
13 | "width": 3914,
14 | "height": 2935,
15 | "url": "https://unsplash.com/photos/Kt5hRENuotI",
16 | "download_url": "https://picsum.photos/id/1018/3914/2935"
17 | },
18 | {
19 | "id": "1019",
20 | "author": "Patrick Fore",
21 | "width": 5472,
22 | "height": 3648,
23 | "url": "https://unsplash.com/photos/V6s1cmE39XM",
24 | "download_url": "https://picsum.photos/id/1019/5472/3648"
25 | },
26 | {
27 | "id": "102",
28 | "author": "Ben Moore",
29 | "width": 4320,
30 | "height": 3240,
31 | "url": "https://unsplash.com/photos/pJILiyPdrXI",
32 | "download_url": "https://picsum.photos/id/102/4320/3240"
33 | },
34 | {
35 | "id": "1020",
36 | "author": "Adam Willoughby-Knox",
37 | "width": 4288,
38 | "height": 2848,
39 | "url": "https://unsplash.com/photos/_snqARKTgoc",
40 | "download_url": "https://picsum.photos/id/1020/4288/2848"
41 | },
42 | {
43 | "id": "1022",
44 | "author": "Vashishtha Jogi",
45 | "width": 6000,
46 | "height": 3376,
47 | "url": "https://unsplash.com/photos/bClr95glx6k",
48 | "download_url": "https://picsum.photos/id/1022/6000/3376"
49 | },
50 | {
51 | "id": "1023",
52 | "author": "William Hook",
53 | "width": 3955,
54 | "height": 2094,
55 | "url": "https://unsplash.com/photos/93Ep1dhTd2s",
56 | "download_url": "https://picsum.photos/id/1023/3955/2094"
57 | },
58 | {
59 | "id": "1024",
60 | "author": "Мартин Тасев",
61 | "width": 1920,
62 | "height": 1280,
63 | "url": "https://unsplash.com/photos/7ALI0RYyq6s",
64 | "download_url": "https://picsum.photos/id/1024/1920/1280"
65 | },
66 | {
67 | "id": "1025",
68 | "author": "Matthew Wiebe",
69 | "width": 4951,
70 | "height": 3301,
71 | "url": "https://unsplash.com/photos/U5rMrSI7Pn4",
72 | "download_url": "https://picsum.photos/id/1025/4951/3301"
73 | },
74 | {
75 | "id": "1018",
76 | "author": "Andrew Ridley",
77 | "width": 3914,
78 | "height": 2935,
79 | "url": "https://unsplash.com/photos/Kt5hRENuotI",
80 | "download_url": "https://picsum.photos/id/1018/3914/2935"
81 | },
82 | {
83 | "id": "1019",
84 | "author": "Patrick Fore",
85 | "width": 5472,
86 | "height": 3648,
87 | "url": "https://unsplash.com/photos/V6s1cmE39XM",
88 | "download_url": "https://picsum.photos/id/1019/5472/3648"
89 | },
90 | {
91 | "id": "102",
92 | "author": "Ben Moore",
93 | "width": 4320,
94 | "height": 3240,
95 | "url": "https://unsplash.com/photos/pJILiyPdrXI",
96 | "download_url": "https://picsum.photos/id/102/4320/3240"
97 | },
98 | {
99 | "id": "1020",
100 | "author": "Adam Willoughby-Knox",
101 | "width": 4288,
102 | "height": 2848,
103 | "url": "https://unsplash.com/photos/_snqARKTgoc",
104 | "download_url": "https://picsum.photos/id/1020/4288/2848"
105 | },
106 | {
107 | "id": "1021",
108 | "author": "Frances Gunn",
109 | "width": 2048,
110 | "height": 1206,
111 | "url": "https://unsplash.com/photos/8BmNurlVR6M",
112 | "download_url": "https://picsum.photos/id/1021/2048/1206"
113 | },
114 | {
115 | "id": "1022",
116 | "author": "Vashishtha Jogi",
117 | "width": 6000,
118 | "height": 3376,
119 | "url": "https://unsplash.com/photos/bClr95glx6k",
120 | "download_url": "https://picsum.photos/id/1022/6000/3376"
121 | },
122 | {
123 | "id": "1023",
124 | "author": "William Hook",
125 | "width": 3955,
126 | "height": 2094,
127 | "url": "https://unsplash.com/photos/93Ep1dhTd2s",
128 | "download_url": "https://picsum.photos/id/1023/3955/2094"
129 | },
130 | {
131 | "id": "1024",
132 | "author": "Мартин Тасев",
133 | "width": 1920,
134 | "height": 1280,
135 | "url": "https://unsplash.com/photos/7ALI0RYyq6s",
136 | "download_url": "https://picsum.photos/id/1024/1920/1280"
137 | },
138 | {
139 | "id": "1025",
140 | "author": "Matthew Wiebe",
141 | "width": 4951,
142 | "height": 3301,
143 | "url": "https://unsplash.com/photos/U5rMrSI7Pn4",
144 | "download_url": "https://picsum.photos/id/1025/4951/3301"
145 | }
146 | ]
--------------------------------------------------------------------------------
/app/src/main/assets/mock_json/picsum_list.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "id": "1016",
4 | "author": "Andrew Ridley",
5 | "width": 3844,
6 | "height": 2563,
7 | "url": "https://unsplash.com/photos/_h7aBovKia4",
8 | "download_url": "https://picsum.photos/id/1018/3914/2935"
9 | },
10 | {
11 | "id": "1018",
12 | "author": "Andrew Ridley",
13 | "width": 3914,
14 | "height": 2935,
15 | "url": "https://unsplash.com/photos/Kt5hRENuotI",
16 | "download_url": "https://picsum.photos/id/1018/3914/2935"
17 | },
18 | {
19 | "id": "1019",
20 | "author": "Patrick Fore",
21 | "width": 5472,
22 | "height": 3648,
23 | "url": "https://unsplash.com/photos/V6s1cmE39XM",
24 | "download_url": "https://picsum.photos/id/1019/5472/3648"
25 | },
26 | {
27 | "id": "102",
28 | "author": "Ben Moore",
29 | "width": 4320,
30 | "height": 3240,
31 | "url": "https://unsplash.com/photos/pJILiyPdrXI",
32 | "download_url": "https://picsum.photos/id/102/4320/3240"
33 | },
34 | {
35 | "id": "1020",
36 | "author": "Adam Willoughby-Knox",
37 | "width": 4288,
38 | "height": 2848,
39 | "url": "https://unsplash.com/photos/_snqARKTgoc",
40 | "download_url": "https://picsum.photos/id/1020/4288/2848"
41 | },
42 | {
43 | "id": "1021",
44 | "author": "Frances Gunn",
45 | "width": 2048,
46 | "height": 1206,
47 | "url": "https://unsplash.com/photos/8BmNurlVR6M",
48 | "download_url": "https://picsum.photos/id/1021/2048/1206"
49 | },
50 | {
51 | "id": "1022",
52 | "author": "Vashishtha Jogi",
53 | "width": 6000,
54 | "height": 3376,
55 | "url": "https://unsplash.com/photos/bClr95glx6k",
56 | "download_url": "https://picsum.photos/id/1022/6000/3376"
57 | },
58 | {
59 | "id": "1023",
60 | "author": "William Hook",
61 | "width": 3955,
62 | "height": 2094,
63 | "url": "https://unsplash.com/photos/93Ep1dhTd2s",
64 | "download_url": "https://picsum.photos/id/1023/3955/2094"
65 | },
66 | {
67 | "id": "1024",
68 | "author": "Мартин Тасев",
69 | "width": 1920,
70 | "height": 1280,
71 | "url": "https://unsplash.com/photos/7ALI0RYyq6s",
72 | "download_url": "https://picsum.photos/id/1024/1920/1280"
73 | },
74 | {
75 | "id": "1025",
76 | "author": "Matthew Wiebe",
77 | "width": 4951,
78 | "height": 3301,
79 | "url": "https://unsplash.com/photos/U5rMrSI7Pn4",
80 | "download_url": "https://picsum.photos/id/1025/4951/3301"
81 | },
82 | {
83 | "id": "1016",
84 | "author": "Andrew Ridley",
85 | "width": 3844,
86 | "height": 2563,
87 | "url": "https://unsplash.com/photos/_h7aBovKia4",
88 | "download_url": "https://picsum.photos/id/1018/3914/2935"
89 | },
90 | {
91 | "id": "1018",
92 | "author": "Andrew Ridley",
93 | "width": 3914,
94 | "height": 2935,
95 | "url": "https://unsplash.com/photos/Kt5hRENuotI",
96 | "download_url": "https://picsum.photos/id/1018/3914/2935"
97 | },
98 | {
99 | "id": "1019",
100 | "author": "Patrick Fore",
101 | "width": 5472,
102 | "height": 3648,
103 | "url": "https://unsplash.com/photos/V6s1cmE39XM",
104 | "download_url": "https://picsum.photos/id/1019/5472/3648"
105 | },
106 | {
107 | "id": "102",
108 | "author": "Ben Moore",
109 | "width": 4320,
110 | "height": 3240,
111 | "url": "https://unsplash.com/photos/pJILiyPdrXI",
112 | "download_url": "https://picsum.photos/id/102/4320/3240"
113 | },
114 | {
115 | "id": "1020",
116 | "author": "Adam Willoughby-Knox",
117 | "width": 4288,
118 | "height": 2848,
119 | "url": "https://unsplash.com/photos/_snqARKTgoc",
120 | "download_url": "https://picsum.photos/id/1020/4288/2848"
121 | },
122 | {
123 | "id": "1021",
124 | "author": "Frances Gunn",
125 | "width": 2048,
126 | "height": 1206,
127 | "url": "https://unsplash.com/photos/8BmNurlVR6M",
128 | "download_url": "https://picsum.photos/id/1021/2048/1206"
129 | },
130 | {
131 | "id": "1022",
132 | "author": "Vashishtha Jogi",
133 | "width": 6000,
134 | "height": 3376,
135 | "url": "https://unsplash.com/photos/bClr95glx6k",
136 | "download_url": "https://picsum.photos/id/1022/6000/3376"
137 | },
138 | {
139 | "id": "1023",
140 | "author": "William Hook",
141 | "width": 3955,
142 | "height": 2094,
143 | "url": "https://unsplash.com/photos/93Ep1dhTd2s",
144 | "download_url": "https://picsum.photos/id/1023/3955/2094"
145 | },
146 | {
147 | "id": "1024",
148 | "author": "Мартин Тасев",
149 | "width": 1920,
150 | "height": 1280,
151 | "url": "https://unsplash.com/photos/7ALI0RYyq6s",
152 | "download_url": "https://picsum.photos/id/1024/1920/1280"
153 | },
154 | {
155 | "id": "1025",
156 | "author": "Matthew Wiebe",
157 | "width": 4951,
158 | "height": 3301,
159 | "url": "https://unsplash.com/photos/U5rMrSI7Pn4",
160 | "download_url": "https://picsum.photos/id/1025/4951/3301"
161 | }
162 | ]
--------------------------------------------------------------------------------
/app/src/main/java/ir/logicbase/mockfit/app/Api.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("HardCodedStringLiteral")
2 |
3 | package ir.logicbase.mockfit.app
4 |
5 | import ir.logicbase.mockfit.Mock
6 | import retrofit2.Call
7 | import retrofit2.http.GET
8 | import retrofit2.http.Query
9 |
10 | interface Api {
11 |
12 | @Mock("picsum_list.json", excludeQueries = ["type={#}"])
13 | @GET("list")
14 | fun getListOfPicsums(
15 | @Query("page") page: Int,
16 | @Query("limit") limit: Int
17 | ): Call>
18 |
19 | @Mock("picsum_favorites.json", includeQueries = ["type=favorites"])
20 | @GET("list")
21 | fun getFavoritePicsums(
22 | @Query("type") type: String,
23 | @Query("page") page: Int,
24 | @Query("limit") limit: Int
25 | ): Call>
26 | }
--------------------------------------------------------------------------------
/app/src/main/java/ir/logicbase/mockfit/app/AppExecutors.kt:
--------------------------------------------------------------------------------
1 | package ir.logicbase.mockfit.app
2 |
3 | import android.os.Handler
4 | import android.os.Looper
5 | import java.util.concurrent.Executor
6 | import java.util.concurrent.Executors
7 |
8 | /**
9 | * Global executor pools for the whole application.
10 | *
11 | * Grouping tasks like this avoids the effects of task starvation (e.g. disk reads don't wait behind
12 | * webservice requests).
13 | */
14 | open class AppExecutors(
15 | private val diskIO: Executor,
16 | private val networkIO: Executor,
17 | private val mainThread: Executor
18 | ) {
19 |
20 | constructor() : this(
21 | Executors.newSingleThreadExecutor(),
22 | Executors.newFixedThreadPool(3),
23 | MainThreadExecutor()
24 | )
25 |
26 | fun diskIO(): Executor {
27 | return diskIO
28 | }
29 |
30 | fun networkIO(): Executor {
31 | return networkIO
32 | }
33 |
34 | fun mainThread(): Executor {
35 | return mainThread
36 | }
37 |
38 | private class MainThreadExecutor : Executor {
39 | private val mainThreadHandler = Handler(Looper.getMainLooper())
40 | override fun execute(command: Runnable) {
41 | mainThreadHandler.post(command)
42 | }
43 | }
44 | }
--------------------------------------------------------------------------------
/app/src/main/java/ir/logicbase/mockfit/app/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package ir.logicbase.mockfit.app
2 |
3 | import android.os.Bundle
4 | import android.widget.Toast
5 | import androidx.appcompat.app.AppCompatActivity
6 | import com.bumptech.glide.Glide
7 | import kotlinx.android.synthetic.main.activity_main.*
8 | import retrofit2.Call
9 | import retrofit2.Callback
10 | import retrofit2.Response
11 |
12 | class MainActivity : AppCompatActivity() {
13 |
14 | private val executors = AppExecutors()
15 | private val dataSource = RemoteDataSource(this)
16 | private var loadingApi = false
17 | private var currentImageIndex = 0
18 |
19 | override fun onCreate(savedInstanceState: Bundle?) {
20 | super.onCreate(savedInstanceState)
21 | setContentView(R.layout.activity_main)
22 | setSupportActionBar(findViewById(R.id.toolbar))
23 |
24 | button_loadRemote.setOnClickListener {
25 | if (!loadingApi) {
26 | dataSource.mockFitEnable = false
27 | loadApiInfo()
28 | }
29 | }
30 |
31 | button_loadMock.setOnClickListener {
32 | if (!loadingApi) {
33 | dataSource.mockFitEnable = true
34 | loadApiInfo()
35 | }
36 | }
37 | }
38 |
39 | private fun loadApiInfo() {
40 | loadingApi = true
41 | textView_author.text = "Loading..."
42 | if (currentImageIndex < 19) {
43 | currentImageIndex++
44 | } else {
45 | currentImageIndex = 0
46 | }
47 | executors.networkIO().execute {
48 | dataSource.api().getFavoritePicsums("favorites", 2, 20).enqueue(object : Callback> {
49 | override fun onResponse(call: Call>, response: Response>) {
50 | loadingApi = false
51 | executors.mainThread().execute {
52 | val picsum = response.body()?.getOrNull(currentImageIndex) ?: return@execute
53 | Glide.with(this@MainActivity).load(picsum.downloadUrl).into(imageView)
54 | textView_author.text = "Image author : ${picsum.author}"
55 | }
56 | }
57 |
58 | override fun onFailure(call: Call>, t: Throwable) {
59 | loadingApi = false
60 | executors.mainThread().execute {
61 | textView_author.text = "Press one of the buttons below"
62 | Toast.makeText(this@MainActivity, "Error loading data", Toast.LENGTH_LONG).show()
63 | }
64 | }
65 | })
66 | }
67 | }
68 | }
--------------------------------------------------------------------------------
/app/src/main/java/ir/logicbase/mockfit/app/MyApplication.kt:
--------------------------------------------------------------------------------
1 | package ir.logicbase.mockfit.app
2 |
3 | import android.app.Application
4 | import android.content.Context
5 | import androidx.multidex.MultiDex
6 |
7 | class MyApplication : Application() {
8 |
9 | override fun attachBaseContext(base: Context?) {
10 | super.attachBaseContext(base)
11 | MultiDex.install(this)
12 | }
13 | }
--------------------------------------------------------------------------------
/app/src/main/java/ir/logicbase/mockfit/app/Picsum.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("HardCodedStringLiteral")
2 |
3 | package ir.logicbase.mockfit.app
4 |
5 | import com.google.gson.annotations.SerializedName
6 |
7 | data class Picsum(
8 | @field:SerializedName("id")
9 | val id: Int,
10 | @field:SerializedName("author")
11 | val author: String,
12 | @field:SerializedName("width")
13 | val width: Int,
14 | @field:SerializedName("height")
15 | val height: Int,
16 | @field:SerializedName("url")
17 | val url: String,
18 | @field:SerializedName("download_url")
19 | val downloadUrl: String
20 | )
--------------------------------------------------------------------------------
/app/src/main/java/ir/logicbase/mockfit/app/RemoteDataSource.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("HardCodedStringLiteral")
2 |
3 | package ir.logicbase.mockfit.app
4 |
5 | import android.content.Context
6 | import android.util.Log
7 | import ir.logicbase.mockfit.MockFitConfig.REQUEST_TO_JSON
8 | import ir.logicbase.mockfit.MockFitInterceptor
9 | import okhttp3.OkHttpClient
10 | import retrofit2.Retrofit
11 | import retrofit2.converter.gson.GsonConverterFactory
12 | import java.util.concurrent.TimeUnit
13 |
14 | class RemoteDataSource(private val context: Context, var mockFitEnable: Boolean = true) {
15 |
16 | fun api(): Api {
17 | val mockFitInterceptor = provideMockFitInterceptor(context)
18 | val okHttpClient = provideOkHttpClient(mockFitInterceptor)
19 | val retrofit = provideRetrofit(okHttpClient)
20 | return retrofit.create(Api::class.java)
21 | }
22 |
23 | private fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit = Retrofit.Builder()
24 | .baseUrl(BASE_URL)
25 | .addConverterFactory(GsonConverterFactory.create())
26 | .client(okHttpClient)
27 | .build()
28 |
29 | private fun provideMockFitInterceptor(context: Context) = MockFitInterceptor(
30 | bodyFactory = { input -> context.resources.assets.open(input) }, // read asset file
31 | logger = { tag, message -> Log.d(tag, message) }, // pass logger to log events in logcat
32 | baseUrl = BASE_URL, // base url of your api
33 | requestPathToMockPathRule = REQUEST_TO_JSON, // autogenerated constant, just press build button
34 | mockFilesPath = MOCK_FILES_PATH, // path to json files
35 | mockFitEnable = mockFitEnable, // master setting to enable or disable mocking
36 | apiEnableMock = true, // enable or disable mock when there are includes and excludes configs
37 | apiIncludeIntoMock = arrayOf(), // include endpoint if `apiEnableMock` is false
38 | apiExcludeFromMock = arrayOf(), // exclude endpoint if `apiEnableMock` is false
39 | apiResponseLatency = 500L // latency of retrieving data
40 | )
41 |
42 | private fun provideOkHttpClient(mockFitInterceptor: MockFitInterceptor) = OkHttpClient.Builder()
43 | .addInterceptor(mockFitInterceptor)
44 | .connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS)
45 | .writeTimeout(WRITE_TIMEOUT, TimeUnit.SECONDS)
46 | .readTimeout(READ_TIMEOUT, TimeUnit.SECONDS)
47 | .build()
48 |
49 | companion object {
50 | private const val BASE_URL = "https://picsum.photos/v2/"
51 | private const val MOCK_FILES_PATH = "mock_json" // located at assets/mock_json/
52 | private const val CONNECT_TIMEOUT = 20L
53 | private const val WRITE_TIMEOUT = 20L
54 | private const val READ_TIMEOUT = 30L
55 | }
56 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
14 |
20 |
21 |
22 |
23 |
27 |
28 |
37 |
38 |
48 |
49 |
59 |
60 |
70 |
71 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/javaherisaber/MockFit/c5fa61ca551e22bae4900105e80ccfd854e94b4a/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/javaherisaber/MockFit/c5fa61ca551e22bae4900105e80ccfd854e94b4a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/javaherisaber/MockFit/c5fa61ca551e22bae4900105e80ccfd854e94b4a/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/javaherisaber/MockFit/c5fa61ca551e22bae4900105e80ccfd854e94b4a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/javaherisaber/MockFit/c5fa61ca551e22bae4900105e80ccfd854e94b4a/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/javaherisaber/MockFit/c5fa61ca551e22bae4900105e80ccfd854e94b4a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/javaherisaber/MockFit/c5fa61ca551e22bae4900105e80ccfd854e94b4a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/javaherisaber/MockFit/c5fa61ca551e22bae4900105e80ccfd854e94b4a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/javaherisaber/MockFit/c5fa61ca551e22bae4900105e80ccfd854e94b4a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/javaherisaber/MockFit/c5fa61ca551e22bae4900105e80ccfd854e94b4a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 | 16dp
3 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | MockFit
3 | Settings
4 |
5 | First Fragment
6 | Second Fragment
7 | Next
8 | Previous
9 |
10 | Hello first fragment
11 | Hello second fragment. Arg: %1$s
12 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
17 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | buildscript {
3 | apply from: 'versions.gradle'
4 | repositories {
5 | google()
6 | jcenter()
7 | }
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:4.1.1'
10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlin"
11 | classpath "com.github.dcendents:android-maven-gradle-plugin:$versions.maven"
12 | // NOTE: Do not place your application dependencies here; they belong
13 | // in the individual module build.gradle files
14 | }
15 | }
16 |
17 | allprojects {
18 | repositories {
19 | google()
20 | jcenter()
21 | }
22 | }
23 |
24 | task clean(type: Delete) {
25 | delete rootProject.buildDir
26 | }
--------------------------------------------------------------------------------
/common/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/common/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'java-library'
3 | id 'com.github.dcendents.android-maven'
4 | id 'kotlin'
5 | }
6 | group='com.github.javaherisaber'
7 |
8 | kotlin {
9 | explicitApi()
10 | }
11 |
12 | java {
13 | sourceCompatibility = JavaVersion.VERSION_1_8
14 | targetCompatibility = JavaVersion.VERSION_1_8
15 | }
16 |
17 | compileKotlin {
18 | kotlinOptions {
19 | jvmTarget = "1.8"
20 | }
21 | }
22 | compileTestKotlin {
23 | kotlinOptions {
24 | jvmTarget = "1.8"
25 | }
26 | }
27 |
28 |
29 | dependencies {
30 | // no dependency
31 | }
32 |
--------------------------------------------------------------------------------
/common/src/main/java/ir/logicbase/mockfit/Mock.kt:
--------------------------------------------------------------------------------
1 | package ir.logicbase.mockfit
2 |
3 | /**
4 | * Annotation meta to mark retrofit endpoints
5 | *
6 | * @property response path to mock file
7 | * @property includeQueries are the queries to separating same paths with different responses based on query values.
8 | * @property excludeQueries are the queries to separating same paths with different responses based on query values.
9 | * For example: ```["limit={#}"]``` or ```["limit"]``` for just including keys not value. For separate them with value you have to pass value as well like ```["limit=20"]```
10 | */
11 | @MustBeDocumented
12 | @Target(AnnotationTarget.FUNCTION)
13 | @Retention(AnnotationRetention.SOURCE)
14 | public annotation class Mock(
15 | val response: String,
16 | val includeQueries: Array = [],
17 | val excludeQueries: Array = [],
18 | )
--------------------------------------------------------------------------------
/common/src/main/java/ir/logicbase/mockfit/MockPathRule.kt:
--------------------------------------------------------------------------------
1 | package ir.logicbase.mockfit
2 |
3 | public data class MockPathRule(
4 | val method: String,
5 | val route: String,
6 | val responseFile: String,
7 | val includeQueries: Array = arrayOf(),
8 | val excludeQueries: Array = arrayOf(),
9 | ) {
10 | override fun equals(other: Any?): Boolean {
11 | if (this === other) return true
12 | if (javaClass != other?.javaClass) return false
13 |
14 | other as MockPathRule
15 |
16 | if (method != other.method) return false
17 | if (route != other.route) return false
18 | if (responseFile != other.responseFile) return false
19 | if (!includeQueries.contentEquals(other.includeQueries)) return false
20 | if (!excludeQueries.contentEquals(other.excludeQueries)) return false
21 |
22 | return true
23 | }
24 |
25 | override fun hashCode(): Int {
26 | var result = method.hashCode()
27 | result = 31 * result + route.hashCode()
28 | result = 31 * result + responseFile.hashCode()
29 | result = 31 * result + includeQueries.contentHashCode()
30 | result = 31 * result + excludeQueries.contentHashCode()
31 | return result
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/common/src/main/java/ir/logicbase/mockfit/StringExtensions.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("HardCodedStringLiteral", "unused")
2 |
3 | package ir.logicbase.mockfit
4 |
5 | /**
6 | * Remove query parameters from given url, for example in this url : `users?q=hello&page=2`
7 | *
8 | * result will be `users`
9 | */
10 | public fun String.removeQueryParams(): String = this.replace("\\?.*".toRegex(), "")
11 |
12 | /**
13 | * Replace resource path from given url, for example in this url : `users/2/form` or `user/cb7d8a83-0b25-4cca-911b-49a1974f1193/form`
14 | *
15 | * result will be `users/{#}/form`
16 | */
17 | public fun String.replaceUrlDynamicPath(): String = this.replace("[\\da-f]{8}-[\\da-f]{4}-4[\\da-f]{3}-[89ab][\\da-f]{3}-[\\da-f]{12}|\\b\\d+((,\\d)+)?".toRegex(), "{#}")
18 |
19 | /**
20 | * Replace resource path from given url, for example in this url : `users/{id}/form`
21 | *
22 | * result will be `users/{#}/form
23 | */
24 | public fun String.replaceEndpointDynamicPath(): String = this.replace("\\{(.*?)\\}".toRegex(), "{#}")
--------------------------------------------------------------------------------
/compiler/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/compiler/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'java-library'
3 | id 'kotlin'
4 | id 'kotlin-kapt'
5 | id 'com.github.dcendents.android-maven'
6 | }
7 | group='com.github.javaherisaber'
8 |
9 | kotlin {
10 | explicitApi()
11 | }
12 |
13 | java {
14 | sourceCompatibility = JavaVersion.VERSION_1_8
15 | targetCompatibility = JavaVersion.VERSION_1_8
16 | }
17 |
18 | compileKotlin {
19 | kotlinOptions {
20 | jvmTarget = "1.8"
21 | }
22 | }
23 | compileTestKotlin {
24 | kotlinOptions {
25 | jvmTarget = "1.8"
26 | }
27 | }
28 |
29 | dependencies {
30 | implementation "com.squareup.retrofit2:retrofit:$versions.retrofit"
31 | implementation "com.squareup:kotlinpoet:$versions.kotlinPoet"
32 | implementation "com.google.auto.service:auto-service:$versions.autoService"
33 | kapt "com.google.auto.service:auto-service:$versions.autoService"
34 | implementation project(':common')
35 | }
--------------------------------------------------------------------------------
/compiler/src/main/java/ir/logicbase/mockfit/MockFitProcessor.kt:
--------------------------------------------------------------------------------
1 | package ir.logicbase.mockfit
2 |
3 | import com.google.auto.service.AutoService
4 | import com.squareup.kotlinpoet.*
5 | import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
6 | import retrofit2.http.*
7 | import java.io.File
8 | import javax.annotation.processing.AbstractProcessor
9 | import javax.annotation.processing.Processor
10 | import javax.annotation.processing.RoundEnvironment
11 | import javax.lang.model.SourceVersion
12 | import javax.lang.model.element.Element
13 | import javax.lang.model.element.TypeElement
14 |
15 | /**
16 | * Kapt processor to generate source code based on functions annotated with [Mock] meta
17 | *
18 | * We are using Google's [AutoService](https://github.com/google/auto/tree/master/service) to run our annotation processor
19 | *
20 | * Also to generate source code we use Square's [KotlinPoet](https://github.com/square/kotlinpoet) library
21 | */
22 | @Suppress("HardCodedStringLiteral")
23 | @AutoService(Processor::class)
24 | internal class MockFitProcessor : AbstractProcessor() {
25 |
26 | private val requestToJsonPath = mutableListOf()
27 |
28 | override fun getSupportedAnnotationTypes(): MutableSet = mutableSetOf(
29 | Mock::class.java.name,
30 | )
31 |
32 | override fun getSupportedSourceVersion(): SourceVersion = SourceVersion.RELEASE_8
33 |
34 | override fun process(
35 | annotations: MutableSet?, roundEnv: RoundEnvironment
36 | ): Boolean {
37 | roundEnv.getElementsAnnotatedWith(Mock::class.java).forEach { processMock(it) }
38 | generateSourceCode()
39 | return false
40 | }
41 |
42 | private fun processMock(element: Element) {
43 | var requestMethodCount = 0
44 | val mockAnnotation = element.getAnnotation(Mock::class.java)
45 | val includeQuery = mockAnnotation.includeQueries
46 | val excludeQuery = mockAnnotation.excludeQueries
47 | val jsonPath = mockAnnotation.response
48 |
49 | element.getAnnotation(GET::class.java)?.let {
50 | requestToJsonPath += MockPathRule("GET", it.value, jsonPath, includeQuery, excludeQuery)
51 | requestMethodCount++
52 | }
53 | element.getAnnotation(POST::class.java)?.let {
54 | requestToJsonPath += MockPathRule("POST", it.value, jsonPath, includeQuery, excludeQuery)
55 | requestMethodCount++
56 | }
57 | element.getAnnotation(PATCH::class.java)?.let {
58 | requestToJsonPath += MockPathRule("PATCH", it.value, jsonPath, includeQuery, excludeQuery)
59 | requestMethodCount++
60 | }
61 | element.getAnnotation(PUT::class.java)?.let {
62 | requestToJsonPath += MockPathRule("PUT", it.value, jsonPath, includeQuery, excludeQuery)
63 | requestMethodCount++
64 | }
65 | element.getAnnotation(HEAD::class.java)?.let {
66 | requestToJsonPath += MockPathRule("HEAD", it.value, jsonPath, includeQuery, excludeQuery)
67 | }
68 | element.getAnnotation(DELETE::class.java)?.let {
69 | requestToJsonPath += MockPathRule("DELETE", it.value, jsonPath, includeQuery, excludeQuery)
70 | requestMethodCount++
71 | }
72 | element.getAnnotation(HTTP::class.java)?.let {
73 | requestToJsonPath += MockPathRule(it.method, it.path, jsonPath, includeQuery, excludeQuery)
74 | }
75 |
76 | if (requestMethodCount > 1) {
77 | val packageName = processingEnv.elementUtils.getPackageOf(element).toString()
78 | val className = element.enclosingElement.simpleName
79 | throw IllegalStateException(
80 | "${packageName}.${className}.${element.simpleName} cannot have multiple method type"
81 | )
82 | }
83 | }
84 |
85 | private fun generateSourceCode() {
86 | val packageName = this.javaClass.`package`.name
87 | val fileName = "MockFitConfig"
88 | val fileBuilder = FileSpec.builder(packageName, fileName)
89 | val classBuilder = TypeSpec.objectBuilder(fileName)
90 | classBuilder.addProperty(
91 | PropertySpec.builder(
92 | "REQUEST_TO_JSON",
93 | Array::class.asClassName().parameterizedBy(
94 | MockPathRule::class.asTypeName()
95 | )
96 | ).initializer(
97 | """
98 | |arrayOf(
99 | | ${generateRequestToJsonPathSource()}
100 | |)
101 | """.trimMargin()
102 | ).addAnnotation(JvmField::class.java)
103 | .build()
104 | )
105 | val file = fileBuilder.addType(classBuilder.build()).build()
106 | val kaptKotlinGeneratedDir = processingEnv.options[KAPT_KOTLIN_GENERATED_OPTION_NAME]
107 | file.writeTo(File(kaptKotlinGeneratedDir!!))
108 | }
109 |
110 | private fun generateRequestToJsonPathSource(): String {
111 | val className = MockPathRule::class.java.name
112 | return requestToJsonPath.joinToString(",") { rule ->
113 | // replace path identifier with # so that interceptor can operate it
114 | val requestPath = rule.route.replaceEndpointDynamicPath()
115 | val includeQueries = """arrayOf(${rule.includeQueries.joinToString(",") { """"$it""""}})"""
116 | val excludeQueries = """arrayOf(${rule.excludeQueries.joinToString(","){ """"$it""""}})"""
117 |
118 | """${className}("${rule.method}", "$requestPath", "${rule.responseFile}", $includeQueries, $excludeQueries)"""
119 | }
120 | }
121 |
122 | companion object {
123 | const val KAPT_KOTLIN_GENERATED_OPTION_NAME = "kapt.kotlin.generated"
124 | }
125 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app"s APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=true
20 | # Kotlin code style for this project: "official" or "obsolete":
21 | kotlin.code.style=official
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/javaherisaber/MockFit/c5fa61ca551e22bae4900105e80ccfd854e94b4a/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Nov 02 16:37:29 IRST 2020
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/runtime/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/runtime/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'java-library'
3 | id 'kotlin'
4 | id 'kotlin-kapt'
5 | id 'com.github.dcendents.android-maven'
6 | }
7 | group='com.github.javaherisaber'
8 |
9 | kotlin {
10 | explicitApi()
11 | }
12 |
13 | java {
14 | sourceCompatibility = JavaVersion.VERSION_1_8
15 | targetCompatibility = JavaVersion.VERSION_1_8
16 | }
17 |
18 | compileKotlin {
19 | kotlinOptions {
20 | jvmTarget = "1.8"
21 | }
22 | }
23 | compileTestKotlin {
24 | kotlinOptions {
25 | jvmTarget = "1.8"
26 | }
27 | }
28 |
29 | dependencies {
30 | implementation "com.squareup.retrofit2:retrofit:$versions.retrofit"
31 | api project(':common')
32 | implementation project(path: ':compiler')
33 | }
--------------------------------------------------------------------------------
/runtime/src/main/java/ir/logicbase/mockfit/BodyFactory.kt:
--------------------------------------------------------------------------------
1 | package ir.logicbase.mockfit
2 |
3 | import java.io.IOException
4 | import java.io.InputStream
5 |
6 | /**
7 | * interface to access InputStream for file operations
8 | */
9 | public fun interface BodyFactory {
10 | @Throws(IOException::class)
11 | public fun create(input: String): InputStream
12 | }
--------------------------------------------------------------------------------
/runtime/src/main/java/ir/logicbase/mockfit/Logger.kt:
--------------------------------------------------------------------------------
1 | package ir.logicbase.mockfit
2 |
3 | /**
4 | * Log messages during intercept operation
5 | */
6 | public fun interface Logger {
7 | public fun log(tag: String, message: String)
8 | }
--------------------------------------------------------------------------------
/runtime/src/main/java/ir/logicbase/mockfit/MockFitInterceptor.kt:
--------------------------------------------------------------------------------
1 | package ir.logicbase.mockfit
2 |
3 | import okhttp3.*
4 | import java.io.IOException
5 |
6 | /**
7 | * Provide network response from json mock in local device
8 | * You should remove `Base Url` and `Query parameters` from [apiIncludeIntoMock] or [apiExcludeFromMock]
9 | * If your request contains `Path parameters` eg. {product_id}, replace it with {#}
10 | *
11 | * @property bodyFactory interface to access InputStream for file operations
12 | * @property logger interface to log messages
13 | * @property baseUrl url of your remote data source
14 | * @property requestPathToMockPathRule generated map in MockFitConfig file
15 | * @property mockFilesPath path to access mock files
16 | * @property mockFitEnable master setting to enable or disable this interceptor
17 | * @property apiEnableMock if enabled it will proceed triggering mock response
18 | * @property apiIncludeIntoMock include specific endpoint into mock
19 | * @property apiExcludeFromMock exclude specific endpoint from being mocked
20 | * @property apiResponseLatency simulate latency while sending response to outer layer
21 | */
22 | @Suppress("HardCodedStringLiteral")
23 | public class MockFitInterceptor constructor(
24 | private val bodyFactory: BodyFactory,
25 | private val logger: Logger,
26 | private val baseUrl: String,
27 | private val requestPathToMockPathRule: Array,
28 | private val mockFilesPath: String = "",
29 | private val mockFitEnable: Boolean = true,
30 | private val apiEnableMock: Boolean = true,
31 | private val apiIncludeIntoMock: Array = arrayOf(),
32 | private val apiExcludeFromMock: Array = arrayOf(),
33 | private val apiResponseLatency: Long = API_DEFAULT_LATENCY
34 | ) : Interceptor {
35 |
36 | @Suppress("DefaultLocale")
37 | @Throws(IOException::class)
38 | override fun intercept(chain: Interceptor.Chain): Response {
39 | val request = chain.request()
40 | if (!mockFitEnable) {
41 | return chain.proceed(request)
42 | }
43 | val url = request.url().toString()
44 | val route = url.replace(baseUrl, "") // remove base url
45 | .removeQueryParams()
46 | .replaceUrlDynamicPath() // replace dynamic path (eg. 3,5,7) with {#}
47 |
48 | // for separate same route with different query and response
49 | val queries = request.url().query()?.split("&") ?: listOf()
50 |
51 | // example requestPath -> [DELETE] shops/{#}/social-accounts/{#}
52 | val requestMethod = request.method().toUpperCase()
53 | val apiMockPathRule = MockPathRule(request.method().toUpperCase(), route, "", arrayOf(), arrayOf())
54 |
55 | var canProceedWithMock = apiEnableMock // can we use mock or proceed with network api
56 |
57 | // check if requestPath is included
58 | for (item in apiIncludeIntoMock) {
59 | val mockPath = item.replaceEndpointDynamicPath()
60 | if (mockPath == apiMockPathRule.route) {
61 | canProceedWithMock = true
62 | break
63 | }
64 | }
65 |
66 | // check if requestPath is excluded
67 | for (item in apiExcludeFromMock) {
68 | if (item.replaceEndpointDynamicPath() == apiMockPathRule.route) {
69 | canProceedWithMock = false
70 | break
71 | }
72 | }
73 |
74 | if (canProceedWithMock) {
75 | // same path but query is different
76 | val currentApiPathRule = requestPathToMockPathRule.lastOrNull {
77 | // remove values and just check query key presence to accept mock json
78 | val removeValueForExcludedQueries = it.excludeQueries.any { q -> q.contains("{#}")}
79 | val removeValueForIncludedQueries = it.includeQueries.any { q -> q.contains("{#}")}
80 |
81 | val eligibleQueries = queries.filter { query ->
82 | if (removeValueForExcludedQueries)
83 | !it.excludeQueries.contains(query.replaceUrlDynamicPath())
84 | else
85 | !it.excludeQueries.contains(query)
86 | }
87 |
88 | it.method == apiMockPathRule.method &&
89 | it.route == apiMockPathRule.route &&
90 | it.includeQueries.all { query ->
91 | eligibleQueries.contains(query)
92 | if (removeValueForIncludedQueries)
93 | eligibleQueries.contains(query.replaceUrlDynamicPath())
94 | else
95 | eligibleQueries.contains(query)
96 | }
97 | }
98 |
99 | val json = getMockJsonOrNull(currentApiPathRule)
100 | return json?.let {
101 | // json is found
102 | logger.log(TAG, "--> Mocking [$requestMethod] $url")
103 | val contentType = MediaType.parse(RESPONSE_MEDIA_TYPE)
104 | val responseBody = ResponseBody.create(contentType, it)
105 | Thread.sleep(apiResponseLatency)
106 | Response.Builder()
107 | .body(responseBody)
108 | .request(request)
109 | .message(RESPONSE_MESSAGE)
110 | .protocol(Protocol.HTTP_1_1)
111 | .code(RESPONSE_CODE)
112 | .build()
113 | } ?: run {
114 | // no json found, proceed request from remote data source
115 | chain.proceed(request)
116 | }
117 | } else {
118 | return chain.proceed(request)
119 | }
120 | }
121 |
122 | /**
123 | * read mock json or if exception occur's return null
124 | *
125 | * @param rule of api path
126 | * @return json in string object
127 | */
128 | @Throws(IOException::class)
129 | private fun getMockJsonOrNull(rule: MockPathRule?): String? {
130 | if (rule == null) return null
131 |
132 | val eligible = requestPathToMockPathRule.firstOrNull() { it == rule }
133 | val fileName = eligible?.responseFile ?: return null
134 | val path = if (mockFilesPath.isEmpty()) fileName else "$mockFilesPath/$fileName"
135 | return bodyFactory.create(path)
136 | .bufferedReader()
137 | .use {
138 | it.readText()
139 | }
140 | }
141 |
142 | private companion object {
143 | private const val TAG = "MockFit"
144 | private const val API_DEFAULT_LATENCY = 150L
145 |
146 | private const val RESPONSE_MEDIA_TYPE = "application/json"
147 | private const val RESPONSE_MESSAGE = "OK"
148 | private const val RESPONSE_CODE = 200
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':runtime'
2 | include ':common'
3 | include ':compiler'
4 | include ':app'
5 | rootProject.name = "MockFit"
--------------------------------------------------------------------------------
/versions.gradle:
--------------------------------------------------------------------------------
1 | ext {
2 | config = [
3 | compileSdk: 29,
4 | minSdk : 17,
5 | targetSdk : 29
6 | ]
7 | versions = [
8 | kotlin : '1.4.10',
9 | material : '1.2.1',
10 | constraintLayout: '1.1.3',
11 | maven : '2.1',
12 | junit : '4.12',
13 | runner : '1.1.0',
14 | espresso : '3.3.0',
15 | retrofit : '2.5.0',
16 | autoService : '1.0-rc7',
17 | kotlinPoet : '1.7.2',
18 | multidex : '2.0.1',
19 | glide : '4.11.0',
20 | ]
21 | }
--------------------------------------------------------------------------------