├── .circleci
└── config.yml
├── .gitignore
├── LICENSE
├── README.md
├── android
├── .gitignore
├── build.gradle
├── lib-proguard-rules.pro
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── io
│ └── appflate
│ └── restmock
│ └── android
│ ├── AndroidAssetsFileParser.java
│ ├── AndroidLocalFileParser.java
│ ├── AndroidLogger.java
│ └── RESTMockTestRunner.java
├── androidsample
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ ├── assets
│ │ └── mocks
│ │ │ └── users
│ │ │ ├── andrzejchm
│ │ │ ├── index.json
│ │ │ └── repos.json
│ │ │ └── user_not_found.json
│ └── java
│ │ └── io
│ │ └── appflate
│ │ └── restmock
│ │ └── androidsample
│ │ ├── CustomTestRunner.java
│ │ ├── TestApplication.java
│ │ ├── pageobjects
│ │ └── MainActivityPageObject.java
│ │ └── tests
│ │ └── MainActivityTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── io
│ │ │ └── appflate
│ │ │ └── restmock
│ │ │ └── androidsample
│ │ │ ├── SampleApplication.java
│ │ │ ├── di
│ │ │ ├── AppComponent.java
│ │ │ └── AppModule.java
│ │ │ ├── domain
│ │ │ └── GithubApi.java
│ │ │ ├── model
│ │ │ ├── Owner.java
│ │ │ ├── Repository.java
│ │ │ └── User.java
│ │ │ ├── utils
│ │ │ └── Utils.java
│ │ │ └── view
│ │ │ ├── activities
│ │ │ ├── MainActivity.java
│ │ │ └── ReposActivity.java
│ │ │ └── adapters
│ │ │ └── ReposRecyclerAdapter.java
│ └── res
│ │ ├── layout
│ │ ├── activity_main.xml
│ │ ├── activity_repos.xml
│ │ └── cell_repo_view.xml
│ │ ├── mipmap-hdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-mdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxxhdpi
│ │ └── ic_launcher.png
│ │ └── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ ├── java
│ └── io
│ │ └── appflate
│ │ └── restmock
│ │ └── androidsample
│ │ └── junit
│ │ └── JUnitExampleTest.java
│ └── resources
│ └── users
│ ├── jwir3
│ ├── profile.json
│ └── repos.json
│ └── user_not_found.json
├── build.gradle
├── core
├── build.gradle
└── src
│ ├── main
│ └── java
│ │ └── io
│ │ └── appflate
│ │ └── restmock
│ │ ├── JVMFileParser.java
│ │ ├── MatchableCall.java
│ │ ├── MatchableCallsRequestDispatcher.java
│ │ ├── MockAnswer.java
│ │ ├── RESTMockFileParser.java
│ │ ├── RESTMockOptions.java
│ │ ├── RESTMockServer.java
│ │ ├── RESTMockServerStarter.java
│ │ ├── RequestsVerifier.java
│ │ ├── SslUtils.java
│ │ ├── exceptions
│ │ ├── RequestInvocationCountMismatchException.java
│ │ ├── RequestInvocationCountNotEnoughException.java
│ │ ├── RequestNotInvokedException.java
│ │ └── RequestVerificationException.java
│ │ ├── logging
│ │ ├── NOOpLogger.java
│ │ └── RESTMockLogger.java
│ │ └── utils
│ │ ├── QueryParam.java
│ │ ├── RequestMatcher.java
│ │ ├── RequestMatchers.java
│ │ └── RestMockUtils.java
│ └── test
│ └── java
│ └── io
│ └── appflate
│ └── restmock
│ ├── QueryParamTest.java
│ ├── RESTMockServerTest.java
│ ├── RequestVerifierTest.java
│ ├── RequestsChainTest.java
│ └── utils
│ ├── RequestMatchersTest.java
│ └── TestUtils.java
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── quality_tools
└── lint.gradle
└── settings.gradle
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | defaults: &defaults
3 | working_directory: ~/code
4 | docker:
5 | - image: circleci/android:api-29
6 | environment:
7 | JVM_OPTS: -Xmx3200m
8 | jobs:
9 | build:
10 | <<: *defaults
11 | steps:
12 | - checkout
13 | - restore_cache:
14 | keys:
15 | - jars-{{ checksum "build.gradle" }}-{{ checksum "core/build.gradle" }}
16 | - jars
17 | - run:
18 | name: Download Dependencies
19 | command: ./gradlew androidDependencies
20 | - run:
21 | name: build sample
22 | command: ./gradlew build -PpreDexLibraries=false
23 | - save_cache:
24 | paths:
25 | - ~/.gradle
26 | key: jars-{{ checksum "build.gradle" }}-{{ checksum "core/build.gradle" }}
27 | check:
28 | <<: *defaults
29 | steps:
30 | - checkout
31 | - restore_cache:
32 | keys:
33 | - checkJars-{{ checksum "build.gradle" }}-{{ checksum "core/build.gradle" }}
34 | - checkJars
35 | - run:
36 | name: Run Check
37 | command: ./gradlew check -PpreDexLibraries=false
38 | - save_cache:
39 | paths:
40 | - ~/.gradle
41 | key: checkJars-{{ checksum "build.gradle" }}-{{ checksum "core/build.gradle" }}
42 | - store_artifacts:
43 | path: core/build/reports
44 | destination: reports-core
45 | - store_artifacts:
46 | path: androidsample/build/reports
47 | destination: reports-sample
48 | - store_test_results:
49 | path: core/build/test-results
50 | - store_test_results:
51 | path: androidsample/build/test-results
52 | #Workflows
53 | workflows:
54 | version: 2
55 | build-and-test:
56 | jobs:
57 | - build
58 | - check
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Eclipse
2 | .classpath
3 | .project
4 | .settings
5 | eclipsebin
6 |
7 | # Ant
8 | bin
9 | gen
10 | build
11 | out
12 | lib
13 |
14 | # Maven
15 | target
16 | pom.xml.*
17 | release.properties
18 | coverage.ec
19 |
20 | # IntelliJ
21 | .idea/*
22 | !.idea/codeStyleSettings.xml
23 | *.iml
24 |
25 |
26 | .DS_Store
27 |
28 | *.class
29 |
30 | # Mobile Tools for Java (J2ME)
31 | .mtj.tmp/
32 |
33 | # Package Files #
34 | *.war
35 | *.ear
36 |
37 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
38 | hs_err_pid*
39 | .idea
40 | .gradle
41 | local.properties
42 | library/build
43 |
--------------------------------------------------------------------------------
/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 |
2 | # RESTMock
3 | [](https://jitpack.io/#andrzejchm/RESTMock)
4 | [](https://android-arsenal.com/details/1/3468) [](https://circleci.com/gh/andrzejchm/RESTMock)
5 |
6 | REST API mocking made easy.
7 |
8 | RESTMock is a library working on top of Square's [okhttp/MockWebServer](https://github.com/square/okhttp/tree/master/mockwebserver). It allows you to specify [Hamcrest](https://github.com/hamcrest/JavaHamcrest) matchers to match HTTP requests and specify what response to return. It is as easy as:
9 |
10 |
11 | ```java
12 | RESTMockServer.whenGET(pathContains("users/andrzejchm"))
13 | .thenReturnFile(200, "users/andrzejchm.json");
14 | ```
15 | **Article**
16 | - [ITDD - Instrumentation TDD for Android](https://medium.com/@andrzejchm/ittd-instrumentation-ttd-for-android-4894cbb82d37)
17 |
18 | ## Table of contents
19 |
20 |
21 |
22 |
23 | - [Setup](#setup)
24 | - [Step 1: Repository](#step-1-repository)
25 | - [Step 2: Dependencies](#step-2-dependencies)
26 | - [Step 3: Start the server](#step-3-start-the-server)
27 | - [a) RESTMockTestRunner](#a-restmocktestrunner)
28 | - [b) RESTMockServerStarter](#b-restmockserverstarter)
29 | - [Step 4: Specify Mocks](#step-4-specify-mocks)
30 | - [a) Files](#a-files)
31 | - [b) Strings](#b-strings)
32 | - [c) MockResponse](#c-mockresponse)
33 | - [c) MockAnswer](#c-mockanswer)
34 | - [Step 5: Request Matchers](#step-5-request-matchers)
35 | - [Step 6: Specify API Endpoint](#step-6-specify-api-endpoint)
36 | - [HTTPS](#https)
37 | - [Response chains](#response-chains)
38 | - [Response delays](#response-delays)
39 | - [Interleaving delays with responses](#interleaving-delays-with-responses)
40 | - [Request verification](#request-verification)
41 | - [Logging](#logging)
42 | - [Android Sample Project](#android-sample-project)
43 | - [Donation](#donation)
44 | - [License](#license)
45 |
46 |
47 |
48 | ## Setup
49 | Here are the basic rules to set up RESTMock for Android
50 |
51 | #### Step 1: Repository
52 | Add it in your root build.gradle at the end of repositories:
53 |
54 | ```groovy
55 | allprojects {
56 | repositories {
57 | ...
58 | maven { url "https://jitpack.io" }
59 | }
60 | }
61 | ```
62 | #### Step 2: Dependencies
63 | Add the dependency
64 |
65 | ```groovy
66 | dependencies {
67 | androidTestImplementation 'com.github.andrzejchm.RESTMock:android:${LATEST_VERSION}' // see "Releases" tab for latest version
68 | }
69 | ```
70 |
71 | #### Step 3: Start the server
72 | It's good to start server before the tested application starts, there are few methods:
73 |
74 | ##### a) RESTMockTestRunner
75 | To make it simple you can just use the predefined `RESTMockTestRunner` in your UI tests. It extends `AndroidJUnitRunner`:
76 |
77 | ```groovy
78 | defaultConfig {
79 | ...
80 | testInstrumentationRunner 'io.appflate.restmock.android.RESTMockTestRunner'
81 | }
82 | ```
83 | ##### b) RESTMockServerStarter
84 | If you have your custom test runner and you can't extend `RESTMockTestRunner`, you can always just call the `RESTMockServerStarter`. Actually `RESTMockTestRunner` is doing exactly the same thing:
85 |
86 | ```java
87 | public class MyAppTestRunner extends AndroidJUnitRunner {
88 | ...
89 | @Override
90 | public void onCreate(Bundle arguments) {
91 | super.onCreate(arguments);
92 | RESTMockServerStarter.startSync(new AndroidAssetsFileParser(getContext()),new AndroidLogger());
93 | ...
94 | }
95 | ...
96 | }
97 |
98 | ```
99 |
100 |
101 | #### Step 4: Specify Mocks
102 |
103 | ##### a) Files
104 | By default, the `RESTMockTestRunner` uses `AndroidAssetsFileParser` as a mocks file parser, which reads the files from the assets folder. To make them visible for the RESTMock you have to put them in the correct folder in your project, for example:
105 |
106 | .../src/androidTest/assets/users/defunkt.json
107 | This can be accessed like this:
108 |
109 | ```java
110 | RESTMockServer.whenGET(pathContains("users/defunkt"))
111 | .thenReturnFile(200, "users/defunkt.json");
112 | ```
113 |
114 | ##### b) Strings
115 | If the response You wish to return is simple, you can just specify a string:
116 |
117 | ```java
118 | RESTMockServer.whenGET(pathContains("users/defunkt"))
119 | .thenReturnString(200, "{}");
120 | ```
121 | ##### c) MockResponse
122 | If you wish to have a greater control over the response, you can pass the `MockResponse`
123 | ```java
124 | RESTMockServer.whenGET(pathContains("users/defunkt")).thenReturn(new MockResponse().setBody("").setResponseCode(401).addHeader("Header","Value"));
125 | ```
126 | ##### c) MockAnswer
127 | You can always build dynamic `MockResponse`s by using the `RecordedRequest` object
128 | ```java
129 | RESTMockServer.whenGET(pathContains("users/defunkt")).thenAnswer(new MockAnswer() {
130 |
131 | @Override
132 | public MockResponse answer(RecordedRequest request) {
133 | return new MockResponse()
134 | .setBody(request.getHeaders().get("header1"))
135 | .setResponseCode(200);
136 | }
137 | });
138 | ```
139 |
140 |
141 | #### Step 5: Request Matchers
142 | You can either use some of the predefined matchers from `RequestMatchers` util class, or create your own. remember to extend from `RequestMatcher`
143 |
144 | #### Step 6: Specify API Endpoint
145 | The most important step, in order for your app to communicate with the testServer, you have to specify it as an endpoint for all your API calls. For that, you can use the ` RESTMockServer.getUrl()`. If you use Retrofit, it is as easy as:
146 |
147 | ```java
148 | RestAdapter adapter = new RestAdapter.Builder()
149 | .baseUrl(RESTMockServer.getUrl())
150 | ...
151 | .build();
152 | ```
153 |
154 | take a look at [#68](https://github.com/andrzejchm/RESTMock/issues/68) for better reference
155 |
156 | ## HTTPS
157 |
158 | By default, RESTMockServer will serve responses using Http. In order to use HTTPS, during initialization you have to pass `RESTMockOptions` object with `useHttps` set to true:
159 | ```java
160 | RESTMockServerStarter.startSync(new AndroidAssetsFileParser(getContext()),new AndroidLogger(), new RESTMockOptions.Builder().useHttps(true).build());
161 | ```
162 |
163 | there is a possibility to set up your own `SSLSocketFactory` and `TrustManager`, if you do not specify those, then default ones will be created for you. You can easly retrieve them with `RESTMockServer.getSSLSocketFactory()` and `RESTMockServer.getTrustManager()` to be able to build your client that will accept the default certificate:
164 |
165 | ```java
166 | new OkHttpClient.Builder().sslSocketFactory(RESTMockServer.getSSLSocketFactory(), RESTMockServer.getTrustManager()).build();
167 | ```
168 |
169 | A sample how to use https with RESTMock in android tests can be found in `androidsample` gradle module within this repository.
170 |
171 | ## Response chains
172 | You can chain different responses for a single request matcher, all the `thenReturn*()` methods accept varags parameter with response, or you can call those methods multiple times on a single matcher, examples:
173 |
174 | ```java
175 | RESTMockServer.whenGET(pathEndsWith(path))
176 | .thenReturnString("a single call")
177 | .thenReturnEmpty(200)
178 | .thenReturnFile("jsonFile.json");
179 | ```
180 |
181 | or
182 |
183 | ```java
184 | RESTMockServer.whenGET(pathEndsWith(path))
185 | .thenReturnString("a single call", "answer no 2", "answer no 3");
186 | ```
187 |
188 | ## Response body delays
189 | Delaying responses is accomplished with the `delayBody(TimeUnit timeUnit, long delay)` and `delayHeaders(TimeUnit timeUnit, long delay)` method. Delays can be specified in chain, just like chaining responses:
190 |
191 | ```java
192 | RESTMockServer.whenGET(pathEndsWith(path))
193 | .thenReturnString("a single call")
194 | .delayBody(TimeUnit.SECONDS, 5)
195 | .delayBody(TimeUnit.SECONDS, 10)
196 | .delayBody(TimeUnit.SECONDS, 15);
197 | ```
198 |
199 | or
200 |
201 | ```java
202 | RESTMockServer.whenGET(pathEndsWith(path))
203 | .thenReturnString("a single call")
204 | .delayBody(TimeUnit.SECONDS, 5, 10, 15);
205 | ```
206 |
207 | Which will result in 1st response body being delayed by 5 seconds, 2nd response by 10 seconds and 3rd, 4th, 5th... by 15 seconds.
208 |
209 | ## Response header delays
210 |
211 | Mechanics of the `responseHeader(...)` method are the same as those in `responseBody(...)`. The only difference is that response headers are being delivered with a delay. This comes handy if your app is acting on response headers, which would've been delivered immediately if you used the `delayBody(...)` method.
212 |
213 |
214 | #### Interleaving delays with responses
215 | Check out this example:
216 |
217 | ```java
218 | RESTMockServer.whenGET(pathEndsWith(path))
219 | .thenReturnString("1st call")
220 | .delay(TimeUnit.SECONDS, 5)
221 | .thenReturnString("2nd call")
222 | .delay(TimeUnit.SECONDS, 10)
223 | .delay(TimeUnit.SECONDS, 15)
224 | .thenReturnString("3rd call")
225 | .delay(TimeUnit.SECONDS, 20, 30, 40)
226 | ```
227 |
228 | this will result in `1st call` being delayed by 5 seconds, `2nd call` delayed by 10 seconds, `3rd call` delayed by 15 seconds, another one by 20 seconds, and another by 30 seconds, and then every consecutive response with 40 seconds delay
229 |
230 | ## Request verification
231 | It is possible to verify which requests were called and how many times thanks to `RequestsVerifier`. All you have to do is call one of these:
232 |
233 | ```java
234 | //cheks if the GET request was invoked exactly 2 times
235 | RequestsVerifier.verifyGET(pathEndsWith("users")).exactly(2);
236 |
237 | //cheks if the GET request was invoked at least 3 times
238 | RequestsVerifier.verifyGET(pathEndsWith("users")).atLeast(3);
239 |
240 | //cheks if the GET request was invoked exactly 1 time
241 | RequestsVerifier.verifyGET(pathEndsWith("users")).invoked();
242 |
243 | //cheks if the GET request was never invoked
244 | RequestsVerifier.verifyGET(pathEndsWith("users")).never();
245 | ```
246 |
247 | Additionaly, you can manualy inspect requests received by RESTMockServer. All you have to do is to obtain them trough:
248 |
249 | ```java
250 | //gets 5 most recent requests received. (ordered from oldest to newest)
251 | RequestsVerifier.takeLast(5);
252 |
253 | //gets 5 oldest requests received. (ordered from oldest to newest)
254 | RequestsVerifier.takeFirst(5);
255 |
256 | //gets all GET requests. (ordered from oldest to newest)
257 | RequestsVerifier.takeAllMatching(isGET());
258 | ```
259 |
260 | ## Logging
261 | RESTMock supports logging events. You just have to provide the RESTMock with the implementation of `RESTMockLogger`. For Android there is an `AndroidLogger` implemented already. All you have to do is use the `RESTMockTestRunner` or call
262 |
263 | ```java
264 | RESTMockServerStarter.startSync(new AndroidAssetsFileParser(getContext()),new AndroidLogger(), new RESTMockOptions());
265 | ```
266 |
267 | or
268 |
269 | ```java
270 | RESTMockServer.enableLogging(RESTMockLogger)
271 | RESTMockServer.disableLogging()
272 | ```
273 |
274 | ## Android Sample Project
275 | You can check out the sample Android app with tests [here](androidsample/)
276 |
277 | ## Donation
278 | If you think the library is awesome and want to buy me a beer, you can do so by sending some...
279 | *  **ETH** here: `0xf7354a0F7B34A380f6d68a2661bE465C10D6AEd7`
280 | *  **BTC** here: `12bU3BMibFqbBBymaftXTDnoHojFymD7a6`
281 | *  **NEO** or **GAS** here: `AX1ovzRN2N28WJrtehjYXjwtHSvcqva6Ri`
282 |
283 | ## License
284 |
285 | Copyright (C) 2016 Appflate.io
286 |
287 | Licensed under the Apache License, Version 2.0 (the "License");
288 | you may not use this file except in compliance with the License.
289 | You may obtain a copy of the License at
290 |
291 | http://www.apache.org/licenses/LICENSE-2.0
292 |
293 | Unless required by applicable law or agreed to in writing, software
294 | distributed under the License is distributed on an "AS IS" BASIS,
295 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
296 | See the License for the specific language governing permissions and
297 | limitations under the License.
298 |
--------------------------------------------------------------------------------
/android/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Appflate.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | apply plugin: 'com.android.library'
18 | apply plugin: 'com.github.dcendents.android-maven'
19 | apply from: '../quality_tools/lint.gradle'
20 |
21 | //noinspection GroovyUnusedAssignment
22 | group = 'com.github.andrzejchm'
23 |
24 | android {
25 | compileSdkVersion rootProject.ext.compileSdk
26 | buildToolsVersion rootProject.ext.buildTools
27 |
28 | defaultConfig {
29 | minSdkVersion rootProject.ext.minSdk
30 | targetSdkVersion rootProject.ext.targetSdk
31 |
32 | consumerProguardFiles "lib-proguard-rules.pro"
33 | }
34 |
35 | buildTypes {
36 | release {
37 | minifyEnabled false
38 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
39 | }
40 | }
41 | }
42 |
43 | dependencies {
44 | implementation fileTree(dir: 'libs', include: ['*.jar'])
45 | testImplementation "junit:junit:$junitVersion"
46 | api(project(':core'))
47 |
48 | implementation "androidx.test:rules:${androidXTestVersion}"
49 | implementation "androidx.test:runner:${androidXTestVersion}"
50 | }
--------------------------------------------------------------------------------
/android/lib-proguard-rules.pro:
--------------------------------------------------------------------------------
1 | -keepclassmembers public class okhttp3.internal.Internal*{
2 | *;
3 | }
4 |
5 |
--------------------------------------------------------------------------------
/android/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/andrzejchm/Library/Android/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/android/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/android/src/main/java/io/appflate/restmock/android/AndroidAssetsFileParser.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Appflate.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.appflate.restmock.android;
18 |
19 | import android.content.Context;
20 |
21 | import java.io.BufferedReader;
22 | import java.io.InputStreamReader;
23 |
24 | import io.appflate.restmock.RESTMockFileParser;
25 |
26 | /**
27 | * Created by andrzejchm on 21/04/16.
28 | */
29 | public class AndroidAssetsFileParser implements RESTMockFileParser {
30 | public static final String UTF_8 = "UTF-8";
31 | private Context testContext;
32 |
33 | public AndroidAssetsFileParser(Context testContext) {
34 | this.testContext = testContext;
35 | }
36 |
37 | @Override
38 | public String readJsonFile(String jsonFilePath) throws Exception {
39 | BufferedReader br = null;
40 |
41 | try {
42 | br = new BufferedReader(new InputStreamReader(
43 | testContext.getAssets().open(
44 | jsonFilePath), UTF_8));
45 | String line;
46 | StringBuilder text = new StringBuilder();
47 |
48 | while ((line = br.readLine()) != null) {
49 | text.append(line);
50 | }
51 | br.close();
52 | return text.toString();
53 | } finally {
54 | if (br != null) {
55 | br.close();
56 | }
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/android/src/main/java/io/appflate/restmock/android/AndroidLocalFileParser.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Scott Johnson, jaywir3@gmail.com
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package io.appflate.restmock.android;
19 |
20 | import android.app.Application;
21 |
22 | import java.io.File;
23 | import java.net.URL;
24 | import java.util.Scanner;
25 |
26 | import io.appflate.restmock.RESTMockFileParser;
27 |
28 | /**
29 | * An implementation of {@link RESTMockFileParser} that allows the retrieval and parsing of files on
30 | * the local filesystem. This does require an Android virtual file system to be set up, so it can be
31 | * used in lieu of the {@link AndroidAssetsFileParser} when running within Unit Tests.
32 | */
33 | public class AndroidLocalFileParser implements RESTMockFileParser {
34 | private Application application;
35 |
36 | public AndroidLocalFileParser(Application application) {
37 | this.application = application;
38 | }
39 |
40 | @Override
41 | public String readJsonFile(String jsonFilePath) throws Exception {
42 | ClassLoader classLoader = application.getClass().getClassLoader();
43 | URL resource = classLoader.getResource(jsonFilePath);
44 | File file = new File(resource.getPath());
45 |
46 | StringBuilder fileContents = new StringBuilder((int) file.length());
47 | Scanner scanner = new Scanner(file, "UTF-8");
48 | String lineSeparator = System.getProperty("line.separator");
49 |
50 | try {
51 | while (scanner.hasNextLine()) {
52 | fileContents.append(scanner.nextLine()).append(lineSeparator);
53 | }
54 | return fileContents.toString();
55 | } finally {
56 | scanner.close();
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/android/src/main/java/io/appflate/restmock/android/AndroidLogger.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Appflate.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.appflate.restmock.android;
18 |
19 | import android.util.Log;
20 |
21 | import io.appflate.restmock.logging.RESTMockLogger;
22 |
23 | /**
24 | * Created by andrzejchm on 23/04/16.
25 | */
26 | public class AndroidLogger implements RESTMockLogger {
27 | private static final String TAG_RESTMOCK = "RESTMock";
28 |
29 | @Override
30 | public void log(String message) {
31 | Log.d(TAG_RESTMOCK, message);
32 | }
33 |
34 | @Override
35 | public void error(String errorMessage) {
36 | Log.e(TAG_RESTMOCK, errorMessage);
37 | }
38 |
39 | @Override
40 | public void error(String errorMessage,
41 | Throwable exception) {
42 | Log.e(TAG_RESTMOCK, errorMessage, exception);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/android/src/main/java/io/appflate/restmock/android/RESTMockTestRunner.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Appflate.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.appflate.restmock.android;
18 |
19 | import android.os.Bundle;
20 | import androidx.test.runner.AndroidJUnitRunner;
21 | import io.appflate.restmock.RESTMockOptions;
22 | import io.appflate.restmock.RESTMockServerStarter;
23 |
24 | /**
25 | * Created by andrzejchm on 22/04/16.
26 | */
27 | public class RESTMockTestRunner extends AndroidJUnitRunner {
28 |
29 | @Override
30 | public void onCreate(Bundle arguments) {
31 | super.onCreate(arguments);
32 | RESTMockServerStarter.startSync(new AndroidAssetsFileParser(getContext()), new AndroidLogger(),
33 | new RESTMockOptions.Builder()
34 | .useHttps(true)
35 | .build());
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/androidsample/build.gradle:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Appflate.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | apply plugin: "com.android.application"
18 | apply from: "../quality_tools/lint.gradle"
19 |
20 | android {
21 | compileSdkVersion rootProject.ext.compileSdk
22 | buildToolsVersion rootProject.ext.buildTools
23 |
24 | testOptions {
25 | unitTests.includeAndroidResources = true
26 | }
27 |
28 | defaultConfig {
29 | applicationId "io.appflate.restmock.androidsample"
30 | minSdkVersion rootProject.ext.minSdk
31 | targetSdkVersion rootProject.ext.targetSdk
32 | versionCode 1
33 | versionName "1.0"
34 | testInstrumentationRunner "io.appflate.restmock.androidsample.CustomTestRunner"
35 |
36 | testInstrumentationRunnerArguments = ["notPackage": "org.bouncycastle.pqc.crypto.qtesla"]
37 | }
38 | buildTypes {
39 | release {
40 | minifyEnabled false
41 | proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
42 | }
43 | }
44 |
45 | compileOptions {
46 | sourceCompatibility JavaVersion.VERSION_1_8
47 | targetCompatibility JavaVersion.VERSION_1_8
48 | }
49 | }
50 |
51 | configurations.all {
52 | resolutionStrategy {
53 | force "androidx.annotation:annotation:1.0.2"
54 | force "com.squareup.okhttp3:okhttp:$okHttpVersion"
55 | }
56 | }
57 |
58 | dependencies {
59 | implementation fileTree(dir: "libs", include: ["*.jar"])
60 | implementation "androidx.appcompat:appcompat:1.3.1"
61 | implementation "com.google.android.material:material:1.4.0"
62 | //noinspection AnnotationProcessorOnCompilePath
63 | implementation "com.jakewharton:butterknife:$butterKnifeVersion"
64 | annotationProcessor "com.jakewharton:butterknife-compiler:$butterKnifeVersion"
65 | implementation "com.google.dagger:dagger:$daggerVersion"
66 | annotationProcessor "com.google.dagger:dagger-compiler:$daggerVersion"
67 | implementation "com.squareup.retrofit2:retrofit:2.9.0"
68 | implementation "com.squareup.retrofit2:converter-gson:2.9.0"
69 | implementation "com.squareup.okhttp3:logging-interceptor:$okHttpVersion"
70 | implementation "com.squareup.okhttp3:okhttp:$okHttpVersion"
71 |
72 | implementation "com.google.code.gson:gson:2.8.6"
73 | implementation "com.google.code.findbugs:annotations:2.0.3"
74 | compileOnly "javax.annotation:jsr250-api:1.0"
75 |
76 | //Test dependencies
77 | androidTestImplementation "androidx.test:rules:$androidXTestVersion"
78 | androidTestImplementation "androidx.test:runner:$androidXTestVersion"
79 |
80 | androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVersion"
81 |
82 | //this dependency tends to break things for most users. Im adding it to check whether the app compiles correctly
83 | androidTestImplementation("androidx.test.espresso:espresso-contrib:$espressoVersion")
84 | androidTestImplementation "androidx.annotation:annotation:1.2.0"
85 |
86 | //the most important, RESTMock! :)
87 | androidTestImplementation project(":android")
88 |
89 | testImplementation "androidx.appcompat:appcompat:1.3.1"
90 | testImplementation "com.google.android.material:material:1.4.0"
91 | testImplementation "junit:junit:$junitVersion"
92 | testImplementation "androidx.test.espresso:espresso-core:$espressoVersion"
93 | testImplementation("androidx.test.espresso:espresso-contrib:$espressoVersion")
94 | testImplementation project(":android")
95 | testImplementation(project(":core"))
96 | }
97 |
--------------------------------------------------------------------------------
/androidsample/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/andrzejchm/Library/Android/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/androidsample/src/androidTest/assets/mocks/users/andrzejchm/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "login": "andrzejchm",
3 | "id": 1711474,
4 | "avatar_url": "https://avatars.githubusercontent.com/u/1711474?v=3",
5 | "gravatar_id": "",
6 | "url": "https://api.github.com/users/andrzejchm",
7 | "html_url": "https://github.com/andrzejchm",
8 | "followers_url": "https://api.github.com/users/andrzejchm/followers",
9 | "following_url": "https://api.github.com/users/andrzejchm/following{/other_user}",
10 | "gists_url": "https://api.github.com/users/andrzejchm/gists{/gist_id}",
11 | "starred_url": "https://api.github.com/users/andrzejchm/starred{/owner}{/repo}",
12 | "subscriptions_url": "https://api.github.com/users/andrzejchm/subscriptions",
13 | "organizations_url": "https://api.github.com/users/andrzejchm/orgs",
14 | "repos_url": "https://api.github.com/users/andrzejchm/repos",
15 | "events_url": "https://api.github.com/users/andrzejchm/events{/privacy}",
16 | "received_events_url": "https://api.github.com/users/andrzejchm/received_events",
17 | "type": "User",
18 | "site_admin": false,
19 | "name": "RESTMock: Andrzej Chmielewski",
20 | "company": null,
21 | "blog": "https://pl.linkedin.com/pub/andrzej-chmielewski/57/208/435",
22 | "location": "Polska",
23 | "email": null,
24 | "hireable": null,
25 | "bio": null,
26 | "public_repos": 3,
27 | "public_gists": 0,
28 | "followers": 0,
29 | "following": 2,
30 | "created_at": "2012-05-06T21:16:22Z",
31 | "updated_at": "2016-04-07T12:08:34Z"
32 | }
--------------------------------------------------------------------------------
/androidsample/src/androidTest/assets/mocks/users/user_not_found.json:
--------------------------------------------------------------------------------
1 | {
2 | "message": "Not Found",
3 | "documentation_url": "https://developer.github.com/v3"
4 | }
--------------------------------------------------------------------------------
/androidsample/src/androidTest/java/io/appflate/restmock/androidsample/CustomTestRunner.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Appflate.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.appflate.restmock.androidsample;
18 |
19 | import android.app.Application;
20 | import android.content.Context;
21 |
22 | import io.appflate.restmock.android.RESTMockTestRunner;
23 |
24 | /**
25 | * Here I'm extending RESTMockTestRunner to change the Application class used within app while testing.
26 | *
27 | * Created by andrzejchm on 23/04/16.
28 | */
29 | public class CustomTestRunner extends RESTMockTestRunner {
30 | @Override
31 | public Application newApplication(ClassLoader cl,
32 | String className,
33 | Context context) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
34 |
35 | //I'm changing the application class only for test purposes. there I'll instantiate AppModule with RESTMock's url.
36 | String testApplicationClassName = TestApplication.class.getCanonicalName();
37 | return super.newApplication(cl, testApplicationClassName, context);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/androidsample/src/androidTest/java/io/appflate/restmock/androidsample/TestApplication.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Appflate.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.appflate.restmock.androidsample;
18 |
19 | import io.appflate.restmock.RESTMockServer;
20 | import io.appflate.restmock.androidsample.di.AppModule;
21 | import io.appflate.restmock.androidsample.di.DaggerAppComponent;
22 |
23 | /**
24 | * Created by andrzejchm on 23/04/16.
25 | */
26 | public class TestApplication extends SampleApplication {
27 |
28 | @Override
29 | protected void setupGraph() {
30 | //here I'm supplying the AppModule with RESTMock's url instead of github's API url
31 | appComponent = DaggerAppComponent.builder()
32 | .appModule(new AppModule(RESTMockServer.getUrl(), RESTMockServer.getSSLSocketFactory(), RESTMockServer.getTrustManager()))
33 | .build();
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/androidsample/src/androidTest/java/io/appflate/restmock/androidsample/pageobjects/MainActivityPageObject.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Appflate.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.appflate.restmock.androidsample.pageobjects;
18 |
19 | import io.appflate.restmock.androidsample.R;
20 |
21 | import static androidx.test.espresso.Espresso.onView;
22 | import static androidx.test.espresso.action.ViewActions.click;
23 | import static androidx.test.espresso.action.ViewActions.replaceText;
24 | import static androidx.test.espresso.assertion.ViewAssertions.matches;
25 | import static androidx.test.espresso.matcher.ViewMatchers.withId;
26 | import static androidx.test.espresso.matcher.ViewMatchers.withText;
27 | import static org.hamcrest.Matchers.containsString;
28 |
29 | /**
30 | * Created by andrzejchm on 23/04/16.
31 | */
32 | public class MainActivityPageObject {
33 | public void typeUsername(String username) {
34 | onView(withId(R.id.usernameEditText)).perform(replaceText(username));
35 | }
36 |
37 | public void pressOk() {
38 | onView(withId(R.id.submitButton)).perform(click());
39 | }
40 |
41 | public void verifyWelcomeMessageForUser(String user) {
42 | onView(withId(R.id.fullNameText)).check(matches(withText(containsString(user))));
43 | }
44 |
45 | public void verifyErrorWelcomeMessage() {
46 | onView(withId(R.id.fullNameText)).check(matches(withText(R.string.something_went_wrong)));
47 | }
48 |
49 | public void clickShowRepos() {
50 | onView(withId(R.id.showReposButton)).perform(click());
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/androidsample/src/androidTest/java/io/appflate/restmock/androidsample/tests/MainActivityTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Appflate.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.appflate.restmock.androidsample.tests;
18 |
19 |
20 | import androidx.test.rule.ActivityTestRule;
21 | import org.junit.Before;
22 | import org.junit.Rule;
23 | import org.junit.Test;
24 |
25 | import io.appflate.restmock.RESTMockServer;
26 | import io.appflate.restmock.RequestsVerifier;
27 | import io.appflate.restmock.androidsample.pageobjects.MainActivityPageObject;
28 | import io.appflate.restmock.androidsample.view.activities.MainActivity;
29 | import okhttp3.mockwebserver.MockResponse;
30 |
31 | import static io.appflate.restmock.utils.RequestMatchers.hasQueryParameterNames;
32 | import static io.appflate.restmock.utils.RequestMatchers.hasQueryParameters;
33 | import static io.appflate.restmock.utils.RequestMatchers.pathEndsWith;
34 | import static io.appflate.restmock.utils.RequestMatchers.pathStartsWith;
35 | import static org.hamcrest.CoreMatchers.not;
36 |
37 | /**
38 | * Created by andrzejchm on 23/04/16.
39 | */
40 | public class MainActivityTest {
41 |
42 | private static final String USERNAME_ANDRZEJCHM = "andrzejchm";
43 | private static final String PATH_ANDRZEJCHM_PROFILE = "mocks/users/andrzejchm/index.json";
44 | private static final String NAME_ANDRZEJ_CHMIELEWSKI = "RESTMock: Andrzej Chmielewski";
45 | private static final String PATH_USER_NOT_FOUND = "mocks/users/user_not_found.json";
46 | private static final String REPOS = "/repos";
47 | @Rule
48 | public ActivityTestRule rule = new ActivityTestRule<>(
49 | MainActivity.class,
50 | true,
51 | false);
52 | private MainActivityPageObject pageObject;
53 |
54 | @Before
55 | public void setUp() {
56 | pageObject = new MainActivityPageObject();
57 | //be sure to reset it before each test!
58 | RESTMockServer.reset();
59 | }
60 |
61 | @Test
62 | public void testGoodAnswer() {
63 | RESTMockServer.whenGET(pathEndsWith(USERNAME_ANDRZEJCHM)).thenReturnFile(
64 | PATH_ANDRZEJCHM_PROFILE);
65 | //launches activity with default intent
66 | rule.launchActivity(null);
67 | pageObject.typeUsername(USERNAME_ANDRZEJCHM);
68 | pageObject.pressOk();
69 | pageObject.verifyWelcomeMessageForUser(NAME_ANDRZEJ_CHMIELEWSKI);
70 | RequestsVerifier.verifyRequest(pathEndsWith(USERNAME_ANDRZEJCHM)).invoked();
71 | RESTMockServer.whenGET(pathStartsWith("login"))
72 | .thenAnswer((request) -> {
73 | if (not(hasQueryParameters()).matches(request)) {
74 | return new MockResponse().setBody("NOT OK").setResponseCode(401);
75 | } else if (hasQueryParameterNames("user","pass").matches(request)) {
76 | return new MockResponse().setBody("OK").setResponseCode(200);
77 | }
78 | return new MockResponse().setBody("NOT MOCKED").setResponseCode(500);
79 | });
80 | }
81 |
82 | @Test
83 | public void testNotFound() {
84 | RESTMockServer.whenGET(pathEndsWith(USERNAME_ANDRZEJCHM)).thenReturnFile(404, PATH_USER_NOT_FOUND);
85 | //launches activity with default intent
86 | rule.launchActivity(null);
87 | pageObject.typeUsername(USERNAME_ANDRZEJCHM);
88 | pageObject.pressOk();
89 | pageObject.verifyErrorWelcomeMessage();
90 | RequestsVerifier.verifyRequest(pathEndsWith(USERNAME_ANDRZEJCHM)).invoked();
91 | }
92 |
93 | @Test
94 | public void testShowEmptyRepos() {
95 | RESTMockServer.whenGET(pathEndsWith(USERNAME_ANDRZEJCHM)).thenReturnFile(
96 | PATH_ANDRZEJCHM_PROFILE);
97 | RESTMockServer.whenGET(pathEndsWith(REPOS)).thenReturnString("[]");
98 | //launches activity with default intent
99 | rule.launchActivity(null);
100 | pageObject.typeUsername(USERNAME_ANDRZEJCHM);
101 | pageObject.pressOk();
102 | pageObject.verifyWelcomeMessageForUser(NAME_ANDRZEJ_CHMIELEWSKI);
103 | pageObject.clickShowRepos();
104 | RequestsVerifier.verifyRequest(pathEndsWith(USERNAME_ANDRZEJCHM)).invoked();
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/androidsample/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
21 |
22 |
23 |
24 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/androidsample/src/main/java/io/appflate/restmock/androidsample/SampleApplication.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Appflate.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.appflate.restmock.androidsample;
18 |
19 | import android.app.Application;
20 |
21 | import io.appflate.restmock.androidsample.di.AppComponent;
22 | import io.appflate.restmock.androidsample.di.AppModule;
23 | import io.appflate.restmock.androidsample.di.DaggerAppComponent;
24 |
25 | /**
26 | * Created by andrzejchm on 22/04/16.
27 | */
28 | public class SampleApplication extends Application {
29 | private static final String BASE_URL = "https://api.github.com/";
30 | static AppComponent appComponent;
31 |
32 | @Override
33 | public void onCreate() {
34 | super.onCreate();
35 | setupGraph();
36 | }
37 |
38 | protected void setupGraph() {
39 | appComponent = DaggerAppComponent.builder()
40 | .appModule(new AppModule(BASE_URL, null, null))
41 | .build();
42 | }
43 |
44 | public static AppComponent getComponent() {
45 | return appComponent;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/androidsample/src/main/java/io/appflate/restmock/androidsample/di/AppComponent.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Appflate.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.appflate.restmock.androidsample.di;
18 |
19 | import javax.inject.Singleton;
20 |
21 | import dagger.Component;
22 | import io.appflate.restmock.androidsample.view.activities.MainActivity;
23 | import io.appflate.restmock.androidsample.view.activities.ReposActivity;
24 | import io.appflate.restmock.androidsample.domain.GithubApi;
25 |
26 | /**
27 | * Created by andrzejchm on 23/04/16.
28 | */
29 | @Singleton
30 | @Component(modules = { AppModule.class})
31 | public interface AppComponent {
32 | GithubApi getRestService();
33 |
34 | void inject(MainActivity mainActivity);
35 | void inject(ReposActivity reposActivity);
36 | }
37 |
--------------------------------------------------------------------------------
/androidsample/src/main/java/io/appflate/restmock/androidsample/di/AppModule.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Appflate.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.appflate.restmock.androidsample.di;
18 |
19 | import dagger.Module;
20 | import dagger.Provides;
21 | import io.appflate.restmock.androidsample.domain.GithubApi;
22 | import javax.net.ssl.SSLSocketFactory;
23 | import javax.net.ssl.X509TrustManager;
24 | import okhttp3.OkHttpClient;
25 | import okhttp3.logging.HttpLoggingInterceptor;
26 | import retrofit2.Retrofit;
27 | import retrofit2.converter.gson.GsonConverterFactory;
28 |
29 | /**
30 | * Created by andrzejchm on 23/04/16.
31 | */
32 | @Module
33 | public class AppModule {
34 |
35 | private String baseUrl;
36 |
37 | private SSLSocketFactory socketFactory;
38 |
39 | private X509TrustManager trustManager;
40 |
41 | public AppModule(String baseUrl, SSLSocketFactory socketFactory, X509TrustManager trustManager) {
42 | this.baseUrl = baseUrl;
43 | this.socketFactory = socketFactory;
44 | this.trustManager = trustManager;
45 | }
46 |
47 | @Provides
48 | GithubApi provideRestService() {
49 | HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
50 | interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
51 | OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder();
52 | if (socketFactory != null && trustManager != null) {
53 | clientBuilder.sslSocketFactory(socketFactory, trustManager);
54 | }
55 | clientBuilder.addInterceptor(interceptor);
56 |
57 | Retrofit retrofit = new Retrofit.Builder().baseUrl(baseUrl)
58 | .client(clientBuilder.build())
59 | .addConverterFactory(GsonConverterFactory.create())
60 | .build();
61 |
62 | return retrofit.create(GithubApi.class);
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/androidsample/src/main/java/io/appflate/restmock/androidsample/domain/GithubApi.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Appflate.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.appflate.restmock.androidsample.domain;
18 |
19 | import java.util.List;
20 |
21 | import io.appflate.restmock.androidsample.model.Repository;
22 | import io.appflate.restmock.androidsample.model.User;
23 | import retrofit2.Call;
24 | import retrofit2.http.GET;
25 | import retrofit2.http.Path;
26 |
27 | /**
28 | * Created by andrzejchm on 22/04/16.
29 | */
30 | public interface GithubApi {
31 |
32 | @GET("users/{username}")
33 | Call getUserProfile(@Path("username") String username);
34 |
35 | @GET("users/{username}/repos")
36 | Call> getUserRepos(@Path("username") String username);
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/androidsample/src/main/java/io/appflate/restmock/androidsample/model/Owner.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Appflate.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.appflate.restmock.androidsample.model;
18 |
19 | import com.google.gson.annotations.SerializedName;
20 |
21 | /**
22 | * Created by andrzejchm on 23/04/16.
23 | */
24 | public class Owner {
25 | @SerializedName("login") public String login;
26 | @SerializedName("id") public int id;
27 | @SerializedName("avatar_url") public String avatarUrl;
28 | @SerializedName("gravatar_id") public String gravatarId;
29 | @SerializedName("url") public String url;
30 | @SerializedName("html_url") public String htmlUrl;
31 | @SerializedName("followers_url") public String followersUrl;
32 | @SerializedName("following_url") public String followingUrl;
33 | @SerializedName("gists_url") public String gistsUrl;
34 | @SerializedName("starred_url") public String starredUrl;
35 | @SerializedName("subscriptions_url") public String subscriptionsUrl;
36 | @SerializedName("organizations_url") public String organizationsUrl;
37 | @SerializedName("repos_url") public String reposUrl;
38 | @SerializedName("events_url") public String eventsUrl;
39 | @SerializedName("received_events_url") public String receivedEventsUrl;
40 | @SerializedName("type") public String type;
41 | @SerializedName("site_admin") public boolean siteAdmin;
42 | }
43 |
--------------------------------------------------------------------------------
/androidsample/src/main/java/io/appflate/restmock/androidsample/model/Repository.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Appflate.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.appflate.restmock.androidsample.model;
18 |
19 | import com.google.gson.annotations.SerializedName;
20 |
21 | /**
22 | * Created by andrzejchm on 23/04/16.
23 | */
24 | public class Repository {
25 | @SerializedName("id") public int id;
26 | @SerializedName("name") public String name;
27 | @SerializedName("full_name") public String fullName;
28 | @SerializedName("owner") public Owner owner;
29 | @SerializedName("private") public boolean _private;
30 | @SerializedName("html_url") public String htmlUrl;
31 | @SerializedName("description") public String description;
32 | @SerializedName("fork") public boolean fork;
33 | @SerializedName("url") public String url;
34 | @SerializedName("forks_url") public String forksUrl;
35 | @SerializedName("keys_url") public String keysUrl;
36 | @SerializedName("collaborators_url") public String collaboratorsUrl;
37 | @SerializedName("teams_url") public String teamsUrl;
38 | @SerializedName("hooks_url") public String hooksUrl;
39 | @SerializedName("issue_events_url") public String issueEventsUrl;
40 | @SerializedName("events_url") public String eventsUrl;
41 | @SerializedName("assignees_url") public String assigneesUrl;
42 | @SerializedName("branches_url") public String branchesUrl;
43 | @SerializedName("tags_url") public String tagsUrl;
44 | @SerializedName("blobs_url") public String blobsUrl;
45 | @SerializedName("git_tags_url") public String gitTagsUrl;
46 | @SerializedName("git_refs_url") public String gitRefsUrl;
47 | @SerializedName("trees_url") public String treesUrl;
48 | @SerializedName("statuses_url") public String statusesUrl;
49 | @SerializedName("languages_url") public String languagesUrl;
50 | @SerializedName("stargazers_url") public String stargazersUrl;
51 | @SerializedName("contributors_url") public String contributorsUrl;
52 | @SerializedName("subscribers_url") public String subscribersUrl;
53 | @SerializedName("subscription_url") public String subscriptionUrl;
54 | @SerializedName("commits_url") public String commitsUrl;
55 | @SerializedName("git_commits_url") public String gitCommitsUrl;
56 | @SerializedName("comments_url") public String commentsUrl;
57 | @SerializedName("issue_comment_url") public String issueCommentUrl;
58 | @SerializedName("contents_url") public String contentsUrl;
59 | @SerializedName("compare_url") public String compareUrl;
60 | @SerializedName("merges_url") public String mergesUrl;
61 | @SerializedName("archive_url") public String archiveUrl;
62 | @SerializedName("downloads_url") public String downloadsUrl;
63 | @SerializedName("issues_url") public String issuesUrl;
64 | @SerializedName("pulls_url") public String pullsUrl;
65 | @SerializedName("milestones_url") public String milestonesUrl;
66 | @SerializedName("notifications_url") public String notificationsUrl;
67 | @SerializedName("labels_url") public String labelsUrl;
68 | @SerializedName("releases_url") public String releasesUrl;
69 | @SerializedName("deployments_url") public String deploymentsUrl;
70 | @SerializedName("created_at") public String createdAt;
71 | @SerializedName("updated_at") public String updatedAt;
72 | @SerializedName("pushed_at") public String pushedAt;
73 | @SerializedName("git_url") public String gitUrl;
74 | @SerializedName("ssh_url") public String sshUrl;
75 | @SerializedName("clone_url") public String cloneUrl;
76 | @SerializedName("svn_url") public String svnUrl;
77 | @SerializedName("homepage") public Object homepage;
78 | @SerializedName("size") public int size;
79 | @SerializedName("stargazers_count") public int stargazersCount;
80 | @SerializedName("watchers_count") public int watchersCount;
81 | @SerializedName("language") public String language;
82 | @SerializedName("has_issues") public boolean hasIssues;
83 | @SerializedName("has_downloads") public boolean hasDownloads;
84 | @SerializedName("has_wiki") public boolean hasWiki;
85 | @SerializedName("has_pages") public boolean hasPages;
86 | @SerializedName("forks_count") public int forksCount;
87 | @SerializedName("mirror_url") public Object mirrorUrl;
88 | @SerializedName("open_issues_count") public int openIssuesCount;
89 | @SerializedName("forks") public int forks;
90 | @SerializedName("open_issues") public int openIssues;
91 | @SerializedName("watchers") public int watchers;
92 | @SerializedName("default_branch") public String defaultBranch;
93 | }
94 |
--------------------------------------------------------------------------------
/androidsample/src/main/java/io/appflate/restmock/androidsample/model/User.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Appflate.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.appflate.restmock.androidsample.model;/**
18 | * Created by andrzejchm on 23/04/16.
19 | */
20 |
21 | import com.google.gson.annotations.SerializedName;
22 |
23 | public class User {
24 | @SerializedName("login") public String login;
25 | @SerializedName("id") public int id;
26 | @SerializedName("avatar_url") public String avatarUrl;
27 | @SerializedName("gravatar_id") public String gravatarId;
28 | @SerializedName("url") public String url;
29 | @SerializedName("html_url") public String htmlUrl;
30 | @SerializedName("followers_url") public String followersUrl;
31 | @SerializedName("following_url") public String followingUrl;
32 | @SerializedName("gists_url") public String gistsUrl;
33 | @SerializedName("starred_url") public String starredUrl;
34 | @SerializedName("subscriptions_url") public String subscriptionsUrl;
35 | @SerializedName("organizations_url") public String organizationsUrl;
36 | @SerializedName("repos_url") public String reposUrl;
37 | @SerializedName("events_url") public String eventsUrl;
38 | @SerializedName("received_events_url") public String receivedEventsUrl;
39 | @SerializedName("type") public String type;
40 | @SerializedName("site_admin") public boolean siteAdmin;
41 | @SerializedName("name") public String name;
42 | @SerializedName("company") public String company;
43 | @SerializedName("blog") public String blog;
44 | @SerializedName("location") public String location;
45 | @SerializedName("email") public String email;
46 | @SerializedName("hireable") public boolean hireable;
47 | @SerializedName("bio") public String bio;
48 | @SerializedName("public_repos") public int publicRepos;
49 | @SerializedName("public_gists") public int publicGists;
50 | @SerializedName("followers") public int followers;
51 | @SerializedName("following") public int following;
52 | @SerializedName("created_at") public String createdAt;
53 | @SerializedName("updated_at") public String updatedAt;
54 | }
--------------------------------------------------------------------------------
/androidsample/src/main/java/io/appflate/restmock/androidsample/utils/Utils.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Appflate.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.appflate.restmock.androidsample.utils;
18 |
19 | import android.app.Activity;
20 | import android.content.Context;
21 | import android.view.inputmethod.InputMethodManager;
22 |
23 | /**
24 | * Created by andrzejchm on 23/04/16.
25 | */
26 | public class Utils {
27 | public static void hideKeyboard(Activity activity) {
28 | InputMethodManager inputManager =
29 | (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
30 | inputManager.hideSoftInputFromWindow(
31 | activity.findViewById(android.R.id.content).getWindowToken(),
32 | InputMethodManager.HIDE_NOT_ALWAYS);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/androidsample/src/main/java/io/appflate/restmock/androidsample/view/activities/MainActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Appflate.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.appflate.restmock.androidsample.view.activities;
18 |
19 | import android.os.Bundle;
20 | import android.view.View;
21 | import android.widget.Button;
22 | import android.widget.EditText;
23 | import android.widget.TextView;
24 | import android.widget.ViewAnimator;
25 |
26 | import java.util.Locale;
27 |
28 | import javax.inject.Inject;
29 |
30 | import androidx.appcompat.app.AppCompatActivity;
31 | import butterknife.BindView;
32 | import butterknife.ButterKnife;
33 | import butterknife.OnClick;
34 | import io.appflate.restmock.androidsample.R;
35 | import io.appflate.restmock.androidsample.SampleApplication;
36 | import io.appflate.restmock.androidsample.domain.GithubApi;
37 | import io.appflate.restmock.androidsample.model.User;
38 | import io.appflate.restmock.androidsample.utils.Utils;
39 | import retrofit2.Call;
40 | import retrofit2.Callback;
41 | import retrofit2.Response;
42 |
43 | public class MainActivity extends AppCompatActivity {
44 |
45 | private static final int POSITION_CONTENT_VIEW = 0;
46 | private static final int POSITION_PROGRESS_VIEW = 1;
47 | @Inject GithubApi githubApi;
48 |
49 | @BindView(R.id.fullNameText) TextView fullNameTextView;
50 | @BindView(R.id.showReposButton) Button showReposButton;
51 | @BindView(R.id.usernameEditText) EditText usernameEditText;
52 | @BindView(R.id.resultAnimator) ViewAnimator resultAnimator;
53 | private String currentUsername;
54 |
55 | @Override
56 | protected void onCreate(Bundle savedInstanceState) {
57 | super.onCreate(savedInstanceState);
58 | setContentView(R.layout.activity_main);
59 | ButterKnife.bind(this);
60 | SampleApplication.getComponent().inject(this);
61 | }
62 |
63 | @OnClick(R.id.submitButton)
64 | public void onSubmitClicked() {
65 | Utils.hideKeyboard(this);
66 | currentUsername = usernameEditText.getText().toString();
67 | resultAnimator.setDisplayedChild(POSITION_PROGRESS_VIEW);
68 | githubApi.getUserProfile(currentUsername).enqueue(new Callback() {
69 | @Override
70 | public void onResponse(Call call,
71 | Response response) {
72 | if (response.isSuccessful()) {
73 | User body = response.body();
74 | String name = body.name == null ? body.login : body.name;
75 | fullNameTextView.setText(String.format(Locale.US,
76 | "Hello %s!",
77 | name));
78 | showReposButton.setVisibility(View.VISIBLE);
79 | resultAnimator.setDisplayedChild(POSITION_CONTENT_VIEW);
80 | } else {
81 | onRequestError();
82 | }
83 | }
84 |
85 | @Override
86 | public void onFailure(Call call,
87 | Throwable t) {
88 | onRequestError();
89 | }
90 | });
91 | }
92 |
93 | @OnClick(R.id.showReposButton)
94 | public void onShowReposClicked() {
95 | startActivity(ReposActivity.intent(this, currentUsername));
96 |
97 | }
98 |
99 | private void onRequestError() {
100 | fullNameTextView.setText(R.string.something_went_wrong);
101 | showReposButton.setVisibility(View.GONE);
102 | resultAnimator.setDisplayedChild(POSITION_CONTENT_VIEW);
103 | }
104 |
105 | public GithubApi getApi() {
106 | return githubApi;
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/androidsample/src/main/java/io/appflate/restmock/androidsample/view/activities/ReposActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Appflate.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.appflate.restmock.androidsample.view.activities;
18 |
19 | import android.app.Activity;
20 | import android.content.Intent;
21 | import android.os.Bundle;
22 | import android.widget.Toast;
23 | import android.widget.ViewAnimator;
24 | import androidx.appcompat.app.AppCompatActivity;
25 | import androidx.recyclerview.widget.LinearLayoutManager;
26 | import androidx.recyclerview.widget.RecyclerView;
27 | import butterknife.BindView;
28 | import butterknife.ButterKnife;
29 | import io.appflate.restmock.androidsample.R;
30 | import io.appflate.restmock.androidsample.SampleApplication;
31 | import io.appflate.restmock.androidsample.domain.GithubApi;
32 | import io.appflate.restmock.androidsample.model.Repository;
33 | import io.appflate.restmock.androidsample.view.adapters.ReposRecyclerAdapter;
34 | import java.util.List;
35 | import javax.inject.Inject;
36 | import retrofit2.Call;
37 | import retrofit2.Callback;
38 | import retrofit2.Response;
39 |
40 | public class ReposActivity extends AppCompatActivity implements Callback> {
41 |
42 | private static final String PARAM_USERNAME = "username";
43 | private String username;
44 |
45 | @Inject GithubApi githubApi;
46 |
47 | @BindView(R.id.reposRecyclerView) RecyclerView reposRecyclerView;
48 | @BindView(R.id.reposAnimator) ViewAnimator reposAnimator;
49 |
50 | @Override
51 | protected void onCreate(Bundle savedInstanceState) {
52 | super.onCreate(savedInstanceState);
53 | setContentView(R.layout.activity_repos);
54 | ButterKnife.bind(this);
55 | SampleApplication.getComponent().inject(this);
56 | if (getIntent() != null) {
57 | username = getIntent().getStringExtra(PARAM_USERNAME);
58 | }
59 | setTitle(username);
60 | githubApi.getUserRepos(username).enqueue(this);
61 | }
62 |
63 | public static Intent intent(Activity activity, String username) {
64 | Intent intent = new Intent(activity, ReposActivity.class);
65 | intent.putExtra(PARAM_USERNAME, username);
66 | return intent;
67 | }
68 |
69 | @Override
70 | public void onResponse(Call> call, Response> response) {
71 | if (response.isSuccessful()) {
72 | reposRecyclerView.setLayoutManager(new LinearLayoutManager(this, RecyclerView.VERTICAL, false));
73 | reposRecyclerView.setAdapter(new ReposRecyclerAdapter(response.body()));
74 | reposAnimator.setDisplayedChild(1);
75 | } else {
76 | onResponseFailure();
77 | }
78 | }
79 |
80 | @Override
81 | public void onFailure(Call> call, Throwable t) {
82 | onResponseFailure();
83 | }
84 |
85 | private void onResponseFailure() {
86 | Toast.makeText(this, "Something went wrong", Toast.LENGTH_LONG).show();
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/androidsample/src/main/java/io/appflate/restmock/androidsample/view/adapters/ReposRecyclerAdapter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Appflate.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.appflate.restmock.androidsample.view.adapters;
18 |
19 | import android.view.LayoutInflater;
20 | import android.view.View;
21 | import android.view.ViewGroup;
22 | import android.widget.TextView;
23 |
24 | import java.util.List;
25 |
26 | import androidx.annotation.NonNull;
27 | import androidx.recyclerview.widget.RecyclerView;
28 | import butterknife.BindView;
29 | import butterknife.ButterKnife;
30 | import io.appflate.restmock.androidsample.R;
31 | import io.appflate.restmock.androidsample.model.Repository;
32 |
33 | /**
34 | * Created by andrzejchm on 23/04/16.
35 | */
36 | public class ReposRecyclerAdapter extends RecyclerView.Adapter {
37 | private final List repositories;
38 |
39 | public ReposRecyclerAdapter(List repositories) {
40 | this.repositories = repositories;
41 | }
42 |
43 | @NonNull
44 | @Override
45 | public RepoViewHolder onCreateViewHolder(@NonNull ViewGroup parent,
46 | int viewType) {
47 | return new RepoViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.cell_repo_view,
48 | parent,
49 | false));
50 | }
51 |
52 | @Override
53 | public void onBindViewHolder(@NonNull RepoViewHolder holder,
54 | int position) {
55 | Repository repository = repositories.get(position);
56 | holder.title.setText(repository.name);
57 | holder.starsCount.setText(String.format("%s ★", repository.stargazersCount));
58 |
59 | }
60 |
61 | @Override
62 | public int getItemCount() {
63 | return repositories.size();
64 | }
65 |
66 | static class RepoViewHolder extends RecyclerView.ViewHolder {
67 | @BindView(R.id.repoTitleText) TextView title;
68 | @BindView(R.id.repoStarsCountText) TextView starsCount;
69 |
70 | RepoViewHolder(View itemView) {
71 | super(itemView);
72 | ButterKnife.bind(this, itemView);
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/androidsample/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
27 |
28 |
32 |
33 |
37 |
38 |
43 |
44 |
45 |
46 |
53 |
54 |
55 |
56 |
63 |
64 |
69 |
70 |
75 |
76 |
77 |
84 |
85 |
--------------------------------------------------------------------------------
/androidsample/src/main/res/layout/activity_repos.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
27 |
28 |
33 |
34 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/androidsample/src/main/res/layout/cell_repo_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
23 |
24 |
30 |
31 |
38 |
39 |
--------------------------------------------------------------------------------
/androidsample/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andrzejchm/RESTMock/7fa3da47a7bc1bc75384ee086d5301ba33664cd0/androidsample/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/androidsample/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andrzejchm/RESTMock/7fa3da47a7bc1bc75384ee086d5301ba33664cd0/androidsample/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/androidsample/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andrzejchm/RESTMock/7fa3da47a7bc1bc75384ee086d5301ba33664cd0/androidsample/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/androidsample/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andrzejchm/RESTMock/7fa3da47a7bc1bc75384ee086d5301ba33664cd0/androidsample/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/androidsample/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andrzejchm/RESTMock/7fa3da47a7bc1bc75384ee086d5301ba33664cd0/androidsample/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/androidsample/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 | #3F51B5
20 | #303F9F
21 | #FF4081
22 |
23 |
--------------------------------------------------------------------------------
/androidsample/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 | RESTMock Sample
19 | Type github username
20 | something went wrong :(
21 | Show Repos
22 |
23 |
--------------------------------------------------------------------------------
/androidsample/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/androidsample/src/test/java/io/appflate/restmock/androidsample/junit/JUnitExampleTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Scott Johnson, jaywir3@gmail.com
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package io.appflate.restmock.androidsample.junit;
18 |
19 | import org.junit.Before;
20 | import org.junit.Test;
21 |
22 | import java.util.List;
23 |
24 | import javax.inject.Inject;
25 |
26 | import io.appflate.restmock.JVMFileParser;
27 | import io.appflate.restmock.RESTMockServer;
28 | import io.appflate.restmock.RESTMockServerStarter;
29 | import io.appflate.restmock.androidsample.domain.GithubApi;
30 | import io.appflate.restmock.androidsample.model.Repository;
31 | import io.appflate.restmock.androidsample.model.User;
32 | import retrofit2.Response;
33 | import retrofit2.Retrofit;
34 | import retrofit2.converter.gson.GsonConverterFactory;
35 |
36 | import static io.appflate.restmock.utils.RequestMatchers.pathEndsWith;
37 | import static junit.framework.Assert.assertEquals;
38 |
39 | public class JUnitExampleTest {
40 |
41 | public static final String USERNAME_JWIR3 = "jwir3";
42 | public static final String PATH_JWIR3_PROFILE = "users/jwir3/profile.json";
43 | public static final String PATH_JWIR3_REPOS = "users/jwir3/repos.json";
44 | private static final String PATH_USER_NOT_FOUND = "users/user_not_found.json";
45 | private static final String REPOS = "/repos";
46 |
47 | // Data about my github profile to check against.
48 | public static final String JWIR3_NAME = "Scott Johnson";
49 | public static final String JWIR3_COMPANY = "Aperture Science";
50 | public static final String JWIR3_BLOG = "www.jwir3.com";
51 | public static final String JWIR3_LOCATION = "Burnsville, MN, USA";
52 | public static final String JWIR3_EMAIL = "jaywir3@gmail.com";
53 |
54 | @Inject
55 | GithubApi api;
56 |
57 | @Before
58 | public void setUp() {
59 | // Be sure to reset the server before each test
60 | RESTMockServerStarter.startSync(new JVMFileParser());
61 | Retrofit retrofit = new Retrofit.Builder()
62 | .baseUrl(RESTMockServer.getUrl())
63 | .addConverterFactory(GsonConverterFactory.create())
64 | .build();
65 |
66 | api = retrofit.create(GithubApi.class);
67 | }
68 |
69 | @Test
70 | public void testValidUser() throws Exception {
71 | RESTMockServer.whenGET(pathEndsWith(USERNAME_JWIR3)).thenReturnFile(200, PATH_JWIR3_PROFILE);
72 |
73 | // Note: This is not recommended in non-test code, since this is a blocking call.
74 | // TODO: Use RxJava magic here to make this easier to show how to accomplish asynchronously.
75 | Response response = api.getUserProfile(USERNAME_JWIR3).execute();
76 | assertEquals(200, response.code());
77 |
78 | User jwir3 = response.body();
79 | assertEquals(JWIR3_NAME, jwir3.name);
80 | assertEquals(JWIR3_BLOG, jwir3.blog);
81 | assertEquals(JWIR3_COMPANY, jwir3.company);
82 | assertEquals(JWIR3_EMAIL, jwir3.email);
83 | assertEquals(JWIR3_LOCATION, jwir3.location);
84 | }
85 |
86 | @Test
87 | public void testNotFound() throws Exception {
88 | RESTMockServer.whenGET(pathEndsWith(USERNAME_JWIR3)).thenReturnFile(404, PATH_USER_NOT_FOUND);
89 |
90 | // Note: This is not recommended in non-test code, since this is a blocking call.
91 | // TODO: Use RxJava magic here to make this easier to show how to accomplish asynchronously.
92 | Response res = api.getUserProfile(USERNAME_JWIR3).execute();
93 | assertEquals(404, res.code());
94 | }
95 |
96 | @Test
97 | public void testShowRepos() throws Exception {
98 | RESTMockServer.whenGET(pathEndsWith(REPOS)).thenReturnFile(200, PATH_JWIR3_REPOS);
99 |
100 | // Note: This is not recommended in non-test code, since this is a blocking call.
101 | // TODO: Use RxJava magic here to make this easier to show how to accomplish asynchronously.
102 | Response> res = api.getUserRepos(USERNAME_JWIR3).execute();
103 | assertEquals(200, res.code());
104 |
105 | List repos = res.body();
106 | assertEquals(29, repos.size());
107 | }
108 | }
--------------------------------------------------------------------------------
/androidsample/src/test/resources/users/jwir3/profile.json:
--------------------------------------------------------------------------------
1 | {
2 | "login": "jwir3",
3 | "id": 997106,
4 | "avatar_url": "https://avatars.githubusercontent.com/u/997106?v=3",
5 | "gravatar_id": "",
6 | "url": "https://api.github.com/users/jwir3",
7 | "html_url": "https://github.com/jwir3",
8 | "followers_url": "https://api.github.com/users/jwir3/followers",
9 | "following_url": "https://api.github.com/users/jwir3/following{/other_user}",
10 | "gists_url": "https://api.github.com/users/jwir3/gists{/gist_id}",
11 | "starred_url": "https://api.github.com/users/jwir3/starred{/owner}{/repo}",
12 | "subscriptions_url": "https://api.github.com/users/jwir3/subscriptions",
13 | "organizations_url": "https://api.github.com/users/jwir3/orgs",
14 | "repos_url": "https://api.github.com/users/jwir3/repos",
15 | "events_url": "https://api.github.com/users/jwir3/events{/privacy}",
16 | "received_events_url": "https://api.github.com/users/jwir3/received_events",
17 | "type": "User",
18 | "site_admin": false,
19 | "name": "Scott Johnson",
20 | "company": "Aperture Science",
21 | "blog": "www.jwir3.com",
22 | "location": "Burnsville, MN, USA",
23 | "email": "jaywir3@gmail.com",
24 | "hireable": true,
25 | "bio": null,
26 | "public_repos": 29,
27 | "public_gists": 8,
28 | "followers": 10,
29 | "following": 22,
30 | "created_at": "2011-08-22T19:18:43Z",
31 | "updated_at": "2016-04-12T00:13:05Z"
32 | }
33 |
--------------------------------------------------------------------------------
/androidsample/src/test/resources/users/user_not_found.json:
--------------------------------------------------------------------------------
1 | {
2 | "message": "Not Found",
3 | "documentation_url": "https://developer.github.com/v3"
4 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | import com.android.build.gradle.internal.tasks.AndroidTestTask
2 |
3 | /*
4 | * Copyright (C) 2016 Appflate.io
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | buildscript {
19 | repositories {
20 | jcenter()
21 | google()
22 | }
23 | dependencies {
24 | classpath 'com.android.tools.build:gradle:3.6.3'
25 | classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1'
26 | }
27 | }
28 |
29 | ext {
30 | libraryVersion = "0.4.4"
31 | compileSdk = 29
32 | targetSdk = 29
33 | minSdk = 21
34 | buildTools = "29.0.2"
35 | okHttpVersion = "4.9.1"
36 | butterKnifeVersion = "10.2.1"
37 | espressoVersion = "3.4.0"
38 | junitVersion = "4.13.2"
39 | androidXTestVersion = "1.4.0"
40 | daggerVersion = "2.27"
41 | }
42 |
43 | allprojects {
44 | repositories {
45 | jcenter()
46 | google()
47 | maven { url "https://jitpack.io" }
48 | maven { url "https://maven.google.com" }
49 | }
50 | version = rootProject.ext.libraryVersion
51 | }
52 |
53 |
54 | ext.preDexLibraries = project.hasProperty('preDexLibraries')
55 |
56 | subprojects {
57 | project.plugins.whenPluginAdded { plugin ->
58 | if ('com.android.build.gradle.AppPlugin'.equals(plugin.class.name) || 'com.android.build.gradle.LibraryPlugin'.equals(
59 | plugin.class.name)) {
60 | // enable or disable pre-dexing
61 | project.android.dexOptions.preDexLibraries = rootProject.ext.preDexLibraries
62 | }
63 | }
64 |
65 | afterEvaluate {
66 | // Log instrumentation tests results.
67 | tasks.withType(AndroidTestTask) { task ->
68 | task.doFirst {
69 | logging.level = LogLevel.INFO
70 | }
71 | task.doLast {
72 | logging.level = LogLevel.LIFECYCLE
73 | }
74 | }
75 | }
76 | }
77 |
78 | task clean(type: Delete) {
79 | delete rootProject.buildDir
80 | }
81 |
--------------------------------------------------------------------------------
/core/build.gradle:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Appflate.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | apply plugin: "java"
18 | apply plugin: "maven"
19 |
20 |
21 | group = "com.github.andrzejchm"
22 |
23 | task sourcesJar(type: Jar, dependsOn: classes) {
24 | classifier = "sources"
25 | from sourceSets.main.allSource
26 | }
27 |
28 | task javadocJar(type: Jar, dependsOn: javadoc) {
29 | classifier = "javadoc"
30 | from javadoc.destinationDir
31 | }
32 |
33 | sourceCompatibility = 1.8
34 | targetCompatibility = 1.8
35 |
36 | artifacts {
37 | archives sourcesJar
38 | archives javadocJar
39 | }
40 |
41 | dependencies {
42 | compile fileTree(dir: "libs", include: ["*.jar"])
43 | compile "com.squareup.okhttp3:mockwebserver:${okHttpVersion}"
44 | compile "com.squareup.okhttp3:okhttp-tls:${okHttpVersion}"
45 | compile "org.hamcrest:hamcrest-core:2.2"
46 |
47 | testCompile "junit:junit:$junitVersion"
48 | testCompile "org.mockito:mockito-all:1.9.5"
49 | testCompile "com.squareup.okhttp3:okhttp:${okHttpVersion}"
50 | testCompile "com.google.code.findbugs:annotations:2.0.3"
51 | }
52 |
53 | jar.dependsOn test
54 | install.dependsOn test
--------------------------------------------------------------------------------
/core/src/main/java/io/appflate/restmock/JVMFileParser.java:
--------------------------------------------------------------------------------
1 | package io.appflate.restmock;
2 |
3 | import java.io.File;
4 | import java.net.URL;
5 | import java.util.Scanner;
6 |
7 | /**
8 | * An implementation of {@link RESTMockFileParser} that allows the retrieval and parsing of files on
9 | * the local filesystem. This does not require an Android dependencies to be set up, so it can be
10 | * used when running within Unit Tests.
11 | */
12 | public class JVMFileParser implements RESTMockFileParser {
13 | @Override
14 | public String readJsonFile(String jsonFilePath) throws Exception {
15 | ClassLoader classLoader = this.getClass().getClassLoader();
16 | URL resource = classLoader.getResource(jsonFilePath);
17 | File file = new File(resource.getPath());
18 | StringBuilder fileContents = new StringBuilder((int)file.length());
19 | Scanner scanner = new Scanner(file, "UTF-8");
20 | String lineSeparator = System.getProperty("line.separator");
21 |
22 | try {
23 | while (scanner.hasNextLine()) {
24 | fileContents.append(scanner.nextLine()).append(lineSeparator);
25 | }
26 | return fileContents.toString();
27 | } finally {
28 | scanner.close();
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/core/src/main/java/io/appflate/restmock/MatchableCallsRequestDispatcher.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Appflate.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.appflate.restmock;
18 |
19 | import java.io.PrintWriter;
20 | import java.io.StringWriter;
21 | import java.util.ArrayList;
22 | import java.util.LinkedList;
23 | import java.util.List;
24 | import java.util.concurrent.CopyOnWriteArrayList;
25 | import okhttp3.mockwebserver.Dispatcher;
26 | import okhttp3.mockwebserver.MockResponse;
27 | import okhttp3.mockwebserver.RecordedRequest;
28 |
29 | class MatchableCallsRequestDispatcher extends Dispatcher {
30 |
31 | private List matchableCalls;
32 | private List requestsHistory = new CopyOnWriteArrayList<>();
33 |
34 | public MatchableCallsRequestDispatcher() {
35 | matchableCalls = new CopyOnWriteArrayList<>();
36 | }
37 |
38 | @Override
39 | public MockResponse dispatch(RecordedRequest recordedRequest) throws InterruptedException {
40 | requestsHistory.add(recordedRequest);
41 | RESTMockServer.getLogger().log("-> New Request:\t" + recordedRequest);
42 | List matchedCalls = getMatchedRequests(recordedRequest);
43 | if (matchedCalls.size() == 1) {
44 | return onOneResponseMatched(recordedRequest, matchedCalls);
45 | } else if (matchedCalls.size() > 1) {
46 | return onTooManyResponsesMatched(recordedRequest, matchedCalls);
47 | } else {
48 | return onNoResponsesMatched(recordedRequest);
49 | }
50 | }
51 |
52 | private MockResponse onOneResponseMatched(RecordedRequest recordedRequest, List matchedRequests) {
53 | MockResponse response = matchedRequests.get(0).nextResponse(recordedRequest);
54 | RESTMockServer.getLogger().log("<- Response:\t" + response);
55 | return response;
56 | }
57 |
58 | private MockResponse onTooManyResponsesMatched(RecordedRequest recordedRequest, List matchedRequests) {
59 | String message = prepareTooManyMatchesMessage(recordedRequest, matchedRequests);
60 | RESTMockServer.getLogger().error("<- Response ERROR:\t" + message);
61 | return createErrorResponse(new IllegalStateException(message));
62 | }
63 |
64 | private MockResponse onNoResponsesMatched(RecordedRequest recordedRequest) {
65 | RESTMockServer.getLogger()
66 | .error("<- Response ERROR:\t"
67 | + RESTMockServer.RESPONSE_NOT_MOCKED
68 | + ": "
69 | + recordedRequest
70 | + "\n list of mocked requests:\n"
71 | + prepareAllMocksMessage());
72 | return createNotMockedResponse(recordedRequest.getMethod());
73 | }
74 |
75 | private String prepareAllMocksMessage() {
76 | StringBuilder sb = new StringBuilder();
77 | for (MatchableCall match : matchableCalls) {
78 | sb.append(match.requestMatcher.toString()).append("\n");
79 | }
80 | return sb.toString();
81 | }
82 |
83 | private MockResponse createNotMockedResponse(String httpMethod) {
84 | MockResponse mockResponse = new MockResponse().setResponseCode(500);
85 | if (!httpMethod.equals("HEAD")) {
86 | mockResponse.setBody(RESTMockServer.RESPONSE_NOT_MOCKED);
87 | }
88 | return mockResponse;
89 | }
90 |
91 | private String prepareTooManyMatchesMessage(RecordedRequest recordedRequest, final List matchedRequests) {
92 | StringBuilder sb = new StringBuilder(RESTMockServer.MORE_THAN_ONE_RESPONSE_ERROR + recordedRequest + ": ");
93 | for (MatchableCall match : matchedRequests) {
94 | sb.append(match.requestMatcher.toString()).append("\n");
95 | }
96 | return sb.toString();
97 | }
98 |
99 | private List getMatchedRequests(RecordedRequest recordedRequest) {
100 | List matched = new LinkedList<>();
101 | for (MatchableCall request : matchableCalls) {
102 | if (request.requestMatcher.matches(recordedRequest)) {
103 | matched.add(request);
104 | }
105 | }
106 | return matched;
107 | }
108 |
109 | MockResponse createErrorResponse(Exception e) {
110 | MockResponse response = new MockResponse();
111 | StringWriter sw = new StringWriter();
112 | PrintWriter pw = new PrintWriter(sw);
113 | e.printStackTrace(pw);
114 | response.setBody(sw.toString());
115 | response.setResponseCode(500);
116 | return response;
117 | }
118 |
119 | void addMatchableCall(MatchableCall matchableCall) {
120 | if (matchableCall.getNumberOfAnswers() > 0) {
121 | RESTMockServer.getLogger().log("## Adding new response for:\t" + matchableCall.requestMatcher);
122 | if (!matchableCalls.contains(matchableCall)) {
123 | matchableCalls.add(matchableCall);
124 | }
125 | } else {
126 | RESTMockServer.getLogger().log("## There were no responses specified for MatchableCall:\t" + matchableCall.requestMatcher);
127 | }
128 | }
129 |
130 | void removeAllMatchableCalls() {
131 | RESTMockServer.getLogger().log("## Removing all responses");
132 | matchableCalls.clear();
133 | }
134 |
135 | boolean removeMatchableCall(final MatchableCall call) {
136 | RESTMockServer.getLogger().log("## Removing response for:\t" + call.requestMatcher);
137 | return matchableCalls.remove(call);
138 | }
139 |
140 | List getRequestHistory() {
141 | return new ArrayList<>(requestsHistory);
142 | }
143 |
144 | void clearHistoricalRequests() {
145 | requestsHistory.clear();
146 | }
147 | }
--------------------------------------------------------------------------------
/core/src/main/java/io/appflate/restmock/MockAnswer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Appflate.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.appflate.restmock;
18 |
19 | import okhttp3.mockwebserver.MockResponse;
20 | import okhttp3.mockwebserver.RecordedRequest;
21 |
22 | /**
23 | * An interface that specifies the answer to return based on the received request
24 | */
25 | public interface MockAnswer {
26 |
27 | /**
28 | * This method gets called by {@link MatchableCallsRequestDispatcher} with a matched {@link RecordedRequest} and should
29 | * return relevant {@link MockResponse}.
30 | *
31 | * @param request a http request that got matched
32 | * @return {@link MockResponse} for the given request.
33 | */
34 | MockResponse answer(RecordedRequest request);
35 | }
36 |
--------------------------------------------------------------------------------
/core/src/main/java/io/appflate/restmock/RESTMockFileParser.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Appflate.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.appflate.restmock;
18 |
19 | /**
20 | * Used for parsing json files from local storage to {@code String}
21 | */
22 | public interface RESTMockFileParser {
23 |
24 | /**
25 | * reads the json file from {@code jsonFilePath} and returns its contents as a {@code String}
26 | *
It's {@code RESTMockFileParser}'s implementation responsibility to determine how to
27 | * resolve the given {@code jsonFilePath}.
28 | *
29 | * @param jsonFilePath a path to json file.
30 | * @return json file's contents as a String.
31 | * @throws Exception when an error occurs while reading the file (f.e. {@link java.io.IOException})
32 | */
33 | String readJsonFile(String jsonFilePath) throws Exception;
34 | }
35 |
--------------------------------------------------------------------------------
/core/src/main/java/io/appflate/restmock/RESTMockOptions.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Appflate.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.appflate.restmock;
18 |
19 | import javax.net.ssl.SSLSocketFactory;
20 | import javax.net.ssl.X509TrustManager;
21 |
22 | public class RESTMockOptions {
23 | private boolean useHttps;
24 | private SSLSocketFactory socketFactory;
25 | private X509TrustManager trustManager;
26 |
27 | private RESTMockOptions(final Builder builder) {
28 | setUseHttps(builder.useHttps);
29 | setSocketFactory(builder.socketFactory);
30 | setTrustManager(builder.trustManager);
31 | }
32 |
33 | public boolean isUseHttps() {
34 | return useHttps;
35 | }
36 |
37 | public void setUseHttps(final boolean useHttps) {
38 | this.useHttps = useHttps;
39 | }
40 |
41 | /**
42 | * Returns the sslSocketFactory used by RESTMockServer. If you didn't specify your own with #setSSlSocketFactory(SSSLSocketFactory)
43 | * method, then a default one will be created
44 | * You can set this socket factory in your HTTP client in order to be able to perform proper SSL handshakes with mockwebserver
45 | *
46 | * @return SSLSocketFactory used by the RESTMockServer
47 | */
48 | public SSLSocketFactory getSocketFactory() {
49 | return socketFactory;
50 | }
51 |
52 | public void setSocketFactory(final SSLSocketFactory socketFactory) {
53 | this.socketFactory = socketFactory;
54 | }
55 |
56 | /**
57 | * Returns the trustManager used in conjunction with sslSocketFactory. It is set up to trust the certificates produces by the
58 | * sslSocketFactory returned in `getSocketFactory().
59 | */
60 | public X509TrustManager getTrustManager() {
61 | return trustManager;
62 | }
63 |
64 | public void setTrustManager(final X509TrustManager trustManager) {
65 | this.trustManager = trustManager;
66 | }
67 |
68 | public static final class Builder {
69 | private boolean useHttps;
70 | private SSLSocketFactory socketFactory;
71 | private X509TrustManager trustManager;
72 |
73 | public Builder() {
74 | }
75 |
76 | public Builder useHttps(final boolean val) {
77 | useHttps = val;
78 | return this;
79 | }
80 |
81 | public Builder socketFactory(final SSLSocketFactory val) {
82 | socketFactory = val;
83 | return this;
84 | }
85 |
86 | public Builder trustManager(final X509TrustManager val) {
87 | trustManager = val;
88 | return this;
89 | }
90 |
91 | public RESTMockOptions build() {
92 | return new RESTMockOptions(this);
93 | }
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/core/src/main/java/io/appflate/restmock/RESTMockServer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Appflate.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.appflate.restmock;
18 |
19 | import io.appflate.restmock.logging.NOOpLogger;
20 | import io.appflate.restmock.logging.RESTMockLogger;
21 | import java.io.IOException;
22 | import javax.net.ssl.SSLSocketFactory;
23 | import javax.net.ssl.X509TrustManager;
24 | import okhttp3.mockwebserver.MockWebServer;
25 | import okhttp3.mockwebserver.RecordedRequest;
26 | import org.hamcrest.Matcher;
27 |
28 | import static io.appflate.restmock.utils.RequestMatchers.isDELETE;
29 | import static io.appflate.restmock.utils.RequestMatchers.isGET;
30 | import static io.appflate.restmock.utils.RequestMatchers.isHEAD;
31 | import static io.appflate.restmock.utils.RequestMatchers.isPATCH;
32 | import static io.appflate.restmock.utils.RequestMatchers.isPOST;
33 | import static io.appflate.restmock.utils.RequestMatchers.isPUT;
34 | import static org.hamcrest.core.AllOf.allOf;
35 |
36 | @SuppressWarnings("unused")
37 | public class RESTMockServer {
38 |
39 | public static final String RESPONSE_NOT_MOCKED = "NOT MOCKED";
40 | public static final String MORE_THAN_ONE_RESPONSE_ERROR = "There are more than one response matching this request: ";
41 | static MockWebServer mockWebServer;
42 | static MatchableCallsRequestDispatcher dispatcher;
43 | private static String serverBaseUrl;
44 | private static RESTMockFileParser RESTMockFileParser;
45 | private static RESTMockLogger logger = new NOOpLogger();
46 | private static SSLSocketFactory socketFactory;
47 | private static X509TrustManager trustManager;
48 |
49 | public static SSLSocketFactory getSSLSocketFactory() {
50 | return socketFactory;
51 | }
52 |
53 | public static X509TrustManager getTrustManager() {
54 | return trustManager;
55 | }
56 |
57 | public static RESTMockLogger getLogger() {
58 | return logger;
59 | }
60 |
61 | public synchronized static void init(RESTMockFileParser restMockFileParser, RESTMockLogger logger) throws IOException {
62 | init(restMockFileParser, logger, new RESTMockOptions.Builder().build());
63 | }
64 |
65 | public synchronized static void init(RESTMockFileParser restMockFileParser, RESTMockLogger logger, RESTMockOptions restMockOptions)
66 | throws IOException {
67 | if (RESTMockServer.mockWebServer != null) {
68 | RESTMockServer.shutdown();
69 | }
70 | RESTMockServer.mockWebServer = new MockWebServer();
71 | if (logger != null) {
72 | RESTMockServer.logger = logger;
73 | }
74 | setUpHttps(restMockOptions);
75 |
76 | RESTMockServer.getLogger().log("## Starting RESTMock server...");
77 | RESTMockServer.dispatcher = new MatchableCallsRequestDispatcher();
78 | RESTMockServer.mockWebServer.setDispatcher(dispatcher);
79 | RESTMockServer.mockWebServer.start();
80 | RESTMockServer.serverBaseUrl = mockWebServer.url("/").toString();
81 | RequestsVerifier.init(dispatcher);
82 |
83 | RESTMockServer.RESTMockFileParser = restMockFileParser;
84 | RESTMockServer.getLogger().log("## RESTMock successfully started!\turl: " + RESTMockServer.serverBaseUrl);
85 | }
86 |
87 | private static void setUpHttps(RESTMockOptions options) {
88 | if (options.isUseHttps()) {
89 | socketFactory = options.getSocketFactory();
90 | if (socketFactory == null) {
91 | socketFactory = SslUtils.localhost().sslSocketFactory();
92 | }
93 | trustManager = options.getTrustManager();
94 | if (trustManager == null) {
95 | trustManager = SslUtils.localhost().trustManager();
96 | }
97 | mockWebServer.useHttps(socketFactory, false);
98 | }
99 | }
100 |
101 | /**
102 | * Enables logging for the RESTMock
103 | *
104 | * @param logger a logger that will be responsible for logging. for Android use AndroidLogger from
105 | * "com.github.andrzejchm.RESTMock:android" dependency
106 | */
107 | public static void enableLogging(RESTMockLogger logger) {
108 | RESTMockServer.logger = logger;
109 | }
110 |
111 | /**
112 | * Disables logging for the RESTMock
113 | */
114 | public static void disableLogging() {
115 | RESTMockServer.logger = new NOOpLogger();
116 | }
117 |
118 | /**
119 | * Use {@link #reset() instead}
120 | */
121 | @Deprecated
122 | public static void removeAllMatchableCalls() {
123 | reset();
124 | }
125 |
126 | /**
127 | * removes all mocks stored in this {@code RESTMockServer} as well as all history requests
128 | */
129 | public static void reset() {
130 | dispatcher.removeAllMatchableCalls();
131 | dispatcher.clearHistoricalRequests();
132 | }
133 |
134 | /**
135 | * removes the given {@code MatchableCall} from this {@code RESTMockServer}
136 | *
137 | * @param call {@code MatchableCall} to be removed
138 | * @return true if the {@code MatchableCall} was successfully removed, false if it was not found
139 | */
140 | public static boolean removeMatchableCall(MatchableCall call) {
141 | return dispatcher.removeMatchableCall(call);
142 | }
143 |
144 | /**
145 | * replaces {@code call} with {@code replacement} in this {@code RESTMockServer}
146 | *
147 | * @param call {@code MatchableCall} to be removed from {@code RESTMockServer}
148 | * @param replacement {@code MatchableCall} to be added to {@code RESTMockServer}
149 | */
150 | public static void replaceMatchableCall(MatchableCall call, MatchableCall replacement) {
151 | removeMatchableCall(call);
152 | dispatcher.addMatchableCall(replacement);
153 | }
154 |
155 | /**
156 | * @return this {@code RESTMockServer} url to use as an endpoint in your tests, or null, if the instance wasn't started yet
157 | */
158 | public static String getUrl() {
159 | return serverBaseUrl;
160 | }
161 |
162 | /**
163 | * adds {@code call} to this {@code RESTMockServer}
164 | *
165 | * @param call to be added to this {@code RESTMockServer}
166 | */
167 | public static void addMatchableCall(final MatchableCall call) {
168 | dispatcher.addMatchableCall(call);
169 | }
170 |
171 | /**
172 | * Helper method to create MatchableCall that will be matched only for GET requests along with the specified {@code requestMatcher}
173 | *
174 | * @param requestMatcher matcher to match a GET request
175 | * @return {@code MatchableCall} that will match GET requests along with {@code requestMatcher}
176 | */
177 | public static MatchableCall whenGET(Matcher requestMatcher) {
178 | return RESTMockServer.whenRequested(allOf(isGET(), requestMatcher));
179 | }
180 |
181 | /**
182 | * Helper method to create MatchableCall that will be matched only for POST requests along with the specified {@code requestMatcher}
183 | *
184 | * @param requestMatcher matcher to match a POST request
185 | * @return {@code MatchableCall} that will match POST requests along with {@code requestMatcher}
186 | */
187 | public static MatchableCall whenPOST(Matcher requestMatcher) {
188 | return RESTMockServer.whenRequested(allOf(isPOST(), requestMatcher));
189 | }
190 |
191 | /**
192 | * Helper method to create MatchableCall that will be matched only for PUT requests along with the specified {@code requestMatcher}
193 | *
194 | * @param requestMatcher matcher to match a PUT request
195 | * @return {@code MatchableCall} that will match PUT requests along with {@code requestMatcher}
196 | */
197 | public static MatchableCall whenPUT(Matcher requestMatcher) {
198 | return RESTMockServer.whenRequested(allOf(isPUT(), requestMatcher));
199 | }
200 |
201 | /**
202 | * Helper method to create MatchableCall that will be matched only for PATCH requests along with the specified {@code requestMatcher}
203 | *
204 | * @param requestMatcher matcher to match a PATCH request
205 | * @return {@code MatchableCall} that will match PATCH requests along with {@code requestMatcher}
206 | */
207 | public static MatchableCall whenPATCH(Matcher requestMatcher) {
208 | return RESTMockServer.whenRequested(allOf(isPATCH(), requestMatcher));
209 | }
210 |
211 | /**
212 | * Helper method to create MatchableCall that will be matched only for DELETE requests along with the specified {@code requestMatcher}
213 | *
214 | * @param requestMatcher matcher to match a DELETE request
215 | * @return {@code MatchableCall} that will match DELETE requests along with {@code requestMatcher}
216 | */
217 | public static MatchableCall whenDELETE(Matcher requestMatcher) {
218 | return RESTMockServer.whenRequested(allOf(isDELETE(), requestMatcher));
219 | }
220 |
221 | public static MatchableCall whenHEAD(Matcher requestMatcher) {
222 | return RESTMockServer.whenRequested(allOf(isHEAD(), requestMatcher));
223 | }
224 |
225 | /**
226 | * Creates a new {@link MatchableCall} for a given {@code requestMatcher}.
227 | * In order to schedule this call within this {@code RESTMockServer},
228 | * be sure to call one of the returned {@code MatchableCall}'s {@code thenReturn*} methods
229 | *
230 | * @param requestMatcher a request matcher to match a HTTP request
231 | * @return a MatchableCall that will get matched by the {@code requestMatcher}
232 | */
233 | public static MatchableCall whenRequested(Matcher requestMatcher) {
234 | return new MatchableCall(RESTMockServer.RESTMockFileParser, requestMatcher, dispatcher);
235 | }
236 |
237 | /**
238 | * Shuts down the instance of RESTMockServer
239 | *
240 | * @throws IOException if something goes wrong while stopping
241 | */
242 | public static void shutdown() throws IOException {
243 | reset();
244 | mockWebServer.shutdown();
245 | }
246 |
247 | private RESTMockServer() {
248 |
249 | }
250 | }
251 |
--------------------------------------------------------------------------------
/core/src/main/java/io/appflate/restmock/RESTMockServerStarter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Appflate.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.appflate.restmock;
18 |
19 | import io.appflate.restmock.logging.RESTMockLogger;
20 | import java.io.IOException;
21 | import java.util.concurrent.LinkedBlockingQueue;
22 | import java.util.concurrent.ThreadPoolExecutor;
23 | import java.util.concurrent.TimeUnit;
24 |
25 | public class RESTMockServerStarter {
26 |
27 | public static final int KEEP_ALIVE_TIME = 60;
28 |
29 | public static void startSync(final RESTMockFileParser mocksFileParser) {
30 | startSync(mocksFileParser, null,
31 | new RESTMockOptions.Builder().build());
32 | }
33 |
34 | public static void startSync(final RESTMockFileParser mocksFileParser, RESTMockOptions options) {
35 | startSync(mocksFileParser, null, options);
36 | }
37 |
38 | public static void startSync(final RESTMockFileParser mocksFileParser, final RESTMockLogger logger) {
39 | startSync(mocksFileParser, logger, new RESTMockOptions.Builder().build());
40 | }
41 |
42 | public static void startSync(
43 | final RESTMockFileParser mocksFileParser,
44 | final RESTMockLogger logger,
45 | final RESTMockOptions restMockOptions
46 | ) {
47 | // it has to be like that since Android prevents starting testServer on main Thread.
48 | ThreadPoolExecutor threadPoolExecutor =
49 | new ThreadPoolExecutor(1, 1, KEEP_ALIVE_TIME, TimeUnit.SECONDS, new LinkedBlockingQueue(1));
50 |
51 | threadPoolExecutor.execute(new Runnable() {
52 |
53 | @Override
54 | public void run() {
55 | try {
56 | RESTMockServer.init(mocksFileParser, logger, restMockOptions);
57 | } catch (IOException e) {
58 | RESTMockServer.getLogger().error("Server start error", e);
59 | throw new RuntimeException(e);
60 | }
61 | }
62 | });
63 | try {
64 | threadPoolExecutor.shutdown();
65 | if (!threadPoolExecutor.awaitTermination(KEEP_ALIVE_TIME, TimeUnit.SECONDS)) {
66 | throw new RuntimeException("mock server didn't manage to start within the given timeout (60 seconds)");
67 | }
68 | } catch (InterruptedException e) {
69 | RESTMockServer.getLogger().error("Server start error", e);
70 | throw new RuntimeException(e);
71 | }
72 | }
73 |
74 | private RESTMockServerStarter() {
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/core/src/main/java/io/appflate/restmock/RequestsVerifier.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Appflate.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.appflate.restmock;
18 |
19 | import org.hamcrest.Matcher;
20 | import org.hamcrest.core.AllOf;
21 |
22 | import java.util.LinkedList;
23 | import java.util.List;
24 |
25 | import io.appflate.restmock.exceptions.RequestInvocationCountMismatchException;
26 | import io.appflate.restmock.exceptions.RequestInvocationCountNotEnoughException;
27 | import io.appflate.restmock.exceptions.RequestNotInvokedException;
28 | import io.appflate.restmock.utils.RequestMatchers;
29 | import okhttp3.mockwebserver.RecordedRequest;
30 |
31 | /**
32 | * Created by andrzejchm on 26/04/16.
33 | */
34 | public class RequestsVerifier {
35 |
36 | public static class RequestVerification {
37 |
38 | Matcher matcher;
39 |
40 | /**
41 | * @param requestMatcher needed to match the interesting request.
42 | * @return how many times the request was invoked
43 | */
44 | private static int requestInvocationCount(Matcher requestMatcher) {
45 | int count = 0;
46 | for (RecordedRequest recordedRequest : dispatcher.getRequestHistory()) {
47 | if (requestMatcher.matches(recordedRequest)) {
48 | count++;
49 | }
50 | }
51 | return count;
52 | }
53 |
54 | RequestVerification(Matcher matcher) {
55 | this.matcher = matcher;
56 | }
57 |
58 | /**
59 | * Checks whether the request matched by {@code requestMatcher} was never called.
60 | * Will throw an exception if it was called.
61 | */
62 | public void never() {
63 | exactly(0);
64 | }
65 |
66 | /**
67 | * {@link #exactly(int)} with the default {@code times} value of 1.
68 | */
69 | public void invoked() {
70 | exactly(1);
71 | }
72 |
73 | /**
74 | * Checks whether the request matched by {@code requestMatcher} was called at least {@code times} times.
75 | *
76 | * @param times At least how many times the request should be invoked.
77 | */
78 | public void atLeast(int times) {
79 | if (times < 1) {
80 | throw new IllegalArgumentException("number of times should be greater than 1! is: " + times);
81 | }
82 | int count = requestInvocationCount(matcher);
83 | if (count < times) {
84 | throw new RequestInvocationCountNotEnoughException(matcher, count, times, dispatcher.getRequestHistory());
85 | }
86 |
87 | }
88 |
89 | /**
90 | * Checks whether the request matched by {@code requestMatcher} was called exactly {@code times} times.
91 | *
92 | * @param times how many times the request was supposed to be invoked.
93 | */
94 | public void exactly(int times) {
95 | checkValidNumberOfTimes(times);
96 | int count = requestInvocationCount(matcher);
97 | if (count != times) {
98 | if (count == 0) {
99 | throw new RequestNotInvokedException(matcher, dispatcher.getRequestHistory());
100 | } else {
101 | throw new RequestInvocationCountMismatchException(count, times, matcher, dispatcher.getRequestHistory());
102 | }
103 | }
104 | }
105 |
106 | private void checkValidNumberOfTimes(int times) {
107 | if (times < 0) {
108 | throw new IllegalArgumentException("number of times should be greater than 0! is: " + times);
109 | }
110 | }
111 |
112 | }
113 |
114 | private static MatchableCallsRequestDispatcher dispatcher;
115 |
116 | static void init(MatchableCallsRequestDispatcher dispatcher) {
117 | RequestsVerifier.dispatcher = dispatcher;
118 | }
119 |
120 | public static RequestVerification verifyRequest(Matcher matcher) {
121 | return new RequestVerification(matcher);
122 | }
123 |
124 | public static RequestVerification verifyDELETE(Matcher matcher) {
125 | return verifyRequest(AllOf.allOf(RequestMatchers.isDELETE(), matcher));
126 | }
127 |
128 | public static RequestVerification verifyGET(Matcher matcher) {
129 | return verifyRequest(AllOf.allOf(RequestMatchers.isGET(), matcher));
130 | }
131 |
132 | public static RequestVerification verifyPATCH(Matcher matcher) {
133 | return verifyRequest(AllOf.allOf(RequestMatchers.isPATCH(), matcher));
134 | }
135 |
136 | public static RequestVerification verifyPOST(Matcher matcher) {
137 | return verifyRequest(AllOf.allOf(RequestMatchers.isPOST(), matcher));
138 | }
139 |
140 | public static RequestVerification verifyPUT(Matcher matcher) {
141 | return verifyRequest(AllOf.allOf(RequestMatchers.isPUT(), matcher));
142 | }
143 |
144 | /**
145 | * @param count number of most recent requests to return from the history of requests received by RESTMockServer.
146 | * @return List of {@code count}-newest requests received by RESTMockServer (from oldest to newest).
147 | */
148 | public static List takeLast(int count) {
149 | List requestHistory = dispatcher.getRequestHistory();
150 | return requestHistory.subList(Math.max(0, requestHistory.size() - count), requestHistory.size());
151 | }
152 |
153 | /**
154 | * @return Most recent request received by RESTMockServer, or null if there were no recorded requests
155 | */
156 | public static RecordedRequest takeLast() {
157 | List lastRequest = takeLast(1);
158 | if (lastRequest.isEmpty()) {
159 | return null;
160 | } else {
161 | return lastRequest.get(0);
162 | }
163 | }
164 |
165 | /**
166 | * @param count number of requests to return from the beginning of the history of requests received by RESTMockServer.
167 | * @return List of {@code count}-oldest requests received by RESTMockServer (from oldest to newest).
168 | */
169 | public static List takeFirst(int count) {
170 | List requestHistory = dispatcher.getRequestHistory();
171 | return requestHistory.subList(0, Math.min(count, requestHistory.size()));
172 | }
173 |
174 | /**
175 | * @return Oldest recorded request received by RESTMockServer, or null if there were no recorded requests
176 | */
177 | public static RecordedRequest takeFirst() {
178 | List lastRequest = takeFirst(1);
179 | if (lastRequest.isEmpty()) {
180 | return null;
181 | } else {
182 | return lastRequest.get(0);
183 | }
184 | }
185 |
186 | /**
187 | * @param fromIndexInclusive low endpoint (inclusive) of the sublist of requests' history.
188 | * @param toIndexExclusive high endpoint (exclusive) of the sublist of requests' history.
189 | * @return specified range of requests' history (from oldest to newest).
190 | */
191 | public static List take(int fromIndexInclusive, int toIndexExclusive) {
192 | return dispatcher.getRequestHistory().subList(fromIndexInclusive, toIndexExclusive);
193 | }
194 |
195 | /**
196 | * @param requestMatcher matcher used to find all relevant requests
197 | * @return a list of requests received by RESTMockServer, that match the given {@code requestMatcher} (from oldest to newest).
198 | */
199 | public static List takeAllMatching(Matcher requestMatcher) {
200 | List result = new LinkedList<>();
201 | for (RecordedRequest recordedRequest : dispatcher.getRequestHistory()) {
202 | if (requestMatcher.matches(recordedRequest)) {
203 | result.add(recordedRequest);
204 | }
205 | }
206 | return result;
207 | }
208 |
209 | private RequestsVerifier() {
210 | throw new UnsupportedOperationException();
211 | }
212 | }
213 |
--------------------------------------------------------------------------------
/core/src/main/java/io/appflate/restmock/SslUtils.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Appflate.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.appflate.restmock;
18 |
19 | import java.net.InetAddress;
20 | import java.net.UnknownHostException;
21 | import okhttp3.tls.HandshakeCertificates;
22 | import okhttp3.tls.HeldCertificate;
23 |
24 |
25 | //DISCLAIMER
26 | // since android does not support ECDSA by default, I've copied this code from OkHttp's tests and replaced the algorithm with RSA
27 | public class SslUtils {
28 |
29 | private static HandshakeCertificates localhost; // Lazily initialized.
30 |
31 | private SslUtils() {
32 | }
33 |
34 | /** Returns an SSL client for this host's localhost address. */
35 | public static synchronized HandshakeCertificates localhost() {
36 | if (localhost != null) return localhost;
37 |
38 | try {
39 | // Generate a self-signed cert for the server to serve and the client to trust.
40 | HeldCertificate heldCertificate = new HeldCertificate.Builder()
41 | .rsa2048()
42 | .commonName("localhost")
43 | .addSubjectAlternativeName(InetAddress.getByName("localhost").getCanonicalHostName())
44 | .build();
45 |
46 | localhost = new HandshakeCertificates.Builder()
47 | .heldCertificate(heldCertificate)
48 | .addTrustedCertificate(heldCertificate.certificate())
49 | .build();
50 |
51 | return localhost;
52 | } catch (UnknownHostException e) {
53 | throw new RuntimeException(e);
54 | }
55 | }
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/core/src/main/java/io/appflate/restmock/exceptions/RequestInvocationCountMismatchException.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Appflate.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.appflate.restmock.exceptions;
18 |
19 | import org.hamcrest.Matcher;
20 |
21 | import java.util.List;
22 |
23 | import okhttp3.mockwebserver.RecordedRequest;
24 |
25 | /**
26 | * Exception called when the RequestVerifier verification fails due to
27 | * count of requests mismatched with expected number.
28 | */
29 | public class RequestInvocationCountMismatchException extends RequestVerificationException {
30 |
31 | public RequestInvocationCountMismatchException(int count, int times, Matcher requestMatcher,
32 | List requestHistory) {
33 |
34 | super(composeMessage("Wanted to be invoked %1$d times, but was %2$d.", requestMatcher, count, times, requestHistory));
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/core/src/main/java/io/appflate/restmock/exceptions/RequestInvocationCountNotEnoughException.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Appflate.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.appflate.restmock.exceptions;
18 |
19 | import org.hamcrest.Matcher;
20 |
21 | import java.util.List;
22 |
23 | import okhttp3.mockwebserver.RecordedRequest;
24 |
25 | /**
26 | * Exception called when the RequestVerifier verification fails due to
27 | * count of requests was not as many as expected number.
28 | */
29 | public class RequestInvocationCountNotEnoughException extends RequestVerificationException {
30 |
31 | public RequestInvocationCountNotEnoughException(Matcher matcher, int count, int times,
32 | List requestHistory) {
33 | super(composeMessage("Wanted to be invoked at least %1$s times, but was only %2$s", matcher, count, times, requestHistory));
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/core/src/main/java/io/appflate/restmock/exceptions/RequestNotInvokedException.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Appflate.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.appflate.restmock.exceptions;
18 |
19 | import org.hamcrest.Matcher;
20 |
21 | import java.util.List;
22 |
23 | import okhttp3.mockwebserver.RecordedRequest;
24 |
25 | /**
26 | * Exception called when the RequestVerifier verification fails due to
27 | * request not being called at least once
28 | */
29 | public class RequestNotInvokedException extends RequestVerificationException {
30 |
31 | public RequestNotInvokedException(Matcher requestMatcher, List requestHistory) {
32 | super(composeMessage("Wanted, but not invoked", requestMatcher, -1, -1, requestHistory));
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/core/src/main/java/io/appflate/restmock/exceptions/RequestVerificationException.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Appflate.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.appflate.restmock.exceptions;
18 |
19 | import org.hamcrest.Matcher;
20 |
21 | import java.util.List;
22 | import java.util.Locale;
23 |
24 | import okhttp3.mockwebserver.RecordedRequest;
25 |
26 | /**
27 | * Created by andrzejchm on 27/04/16.
28 | */
29 | public class RequestVerificationException extends RuntimeException {
30 |
31 | protected static String composeMessage(String messageFormat, Matcher requestMatcher, int count, int times,
32 | List requestHistory) {
33 | StringBuilder sb = new StringBuilder();
34 | sb.append("Request = \"").append(requestMatcher).append("\":\n");
35 | sb.append(String.format(Locale.US, messageFormat, times, count));
36 | appendHistoryRequests(requestMatcher, requestHistory, sb);
37 | return sb.toString();
38 | }
39 |
40 | protected static void appendHistoryRequests(Matcher matcher, List requestHistory, StringBuilder sb) {
41 | sb.append("\n\nAll invocations: (\"#\" at the beginning means the request was matched)\n[");
42 | if (!requestHistory.isEmpty()) {
43 | sb.append("\n");
44 | }
45 | for (RecordedRequest recordedRequest : requestHistory) {
46 | sb.append("\t");
47 | if (matcher.matches(recordedRequest)) {
48 | sb.append("# ");
49 | }
50 | sb.append(recordedRequest.getRequestLine());
51 | if (matcher.matches(recordedRequest)) {
52 | sb.append(" \t| #MATCH");
53 | }
54 | sb.append("\n");
55 | }
56 | sb.append("]");
57 | }
58 |
59 | public RequestVerificationException(String message) {
60 | super(message);
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/core/src/main/java/io/appflate/restmock/logging/NOOpLogger.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Appflate.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.appflate.restmock.logging;
18 |
19 | /**
20 | * Created by andrzejchm on 23/04/16.
21 | */
22 | public class NOOpLogger implements RESTMockLogger {
23 |
24 | @Override
25 | public void log(String message) {
26 | //intentionally empty
27 | }
28 |
29 | @Override
30 | public void error(String errorMessage) {
31 | //intentionally empty
32 | }
33 |
34 | @Override
35 | public void error(String errorMessage, Throwable exception) {
36 | //intentionally empty
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/core/src/main/java/io/appflate/restmock/logging/RESTMockLogger.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Appflate.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.appflate.restmock.logging;
18 |
19 | /**
20 | * Created by andrzejchm on 23/04/16.
21 | */
22 | public interface RESTMockLogger {
23 |
24 | void log(String message);
25 |
26 | void error(String errorMessage);
27 |
28 | void error(String errorMessage, Throwable exception);
29 | }
30 |
--------------------------------------------------------------------------------
/core/src/main/java/io/appflate/restmock/utils/QueryParam.java:
--------------------------------------------------------------------------------
1 | package io.appflate.restmock.utils;
2 |
3 | import java.util.LinkedList;
4 | import java.util.List;
5 |
6 | /**
7 | * A key/value set representing query parameters in an HTTP request.
8 | */
9 | public class QueryParam {
10 |
11 | private final String key;
12 | private final List values;
13 |
14 | public QueryParam(String key, List values) {
15 | this.key = key;
16 | this.values = values;
17 | }
18 |
19 | public QueryParam(String key, String... values) {
20 | this.key = key;
21 | this.values = new LinkedList<>();
22 |
23 | for (String val : values) {
24 | this.values.add(val);
25 | }
26 | }
27 |
28 | @Override
29 | public boolean equals(Object other) {
30 | if (!(other instanceof QueryParam)) {
31 | return false;
32 | }
33 |
34 | QueryParam otherQueryParam = (QueryParam) other;
35 |
36 | if (this == other) {
37 | return true;
38 | }
39 |
40 | if (!this.key.equals(otherQueryParam.key)) {
41 | return false;
42 | }
43 |
44 | if (this.values.size() != otherQueryParam.values.size()) {
45 | return false;
46 | }
47 |
48 | for (String nextVal : this.values) {
49 | if (!otherQueryParam.values.contains(nextVal)) {
50 | return false;
51 | }
52 | }
53 |
54 | return true;
55 | }
56 |
57 | @Override
58 | public int hashCode() {
59 | int result = 17;
60 | result = 37 * result + (key == null ? 0 : key.hashCode());
61 | result = 37 * result + (values == null ? 0 : values.hashCode());
62 |
63 | return result;
64 | }
65 |
66 | public String getKey() {
67 | return this.key;
68 | }
69 |
70 | public List getValues() {
71 | return this.values;
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/core/src/main/java/io/appflate/restmock/utils/RequestMatcher.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Appflate.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.appflate.restmock.utils;
18 |
19 | import org.hamcrest.Description;
20 | import org.hamcrest.TypeSafeMatcher;
21 |
22 | import okhttp3.mockwebserver.RecordedRequest;
23 |
24 | /**
25 | *
A RequestMatcher is an extension of {@link org.hamcrest.TypeSafeMatcher} making it easier to specify
26 | * the matcher without the need of implementing {@link #describeTo(Description)} method.
27 | *
See {@link org.hamcrest.Matcher} for more info about hamcrest's matchers
28 | */
29 | public abstract class RequestMatcher extends TypeSafeMatcher {
30 |
31 | private final String description;
32 |
33 | public RequestMatcher(String description) {
34 | this.description = description;
35 | }
36 |
37 | @Override
38 | public void describeTo(Description description) {
39 | description.appendText(this.description);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/core/src/main/java/io/appflate/restmock/utils/RequestMatchers.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Appflate.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.appflate.restmock.utils;
18 |
19 | import java.io.UnsupportedEncodingException;
20 | import java.net.MalformedURLException;
21 | import java.net.URL;
22 | import java.util.ArrayList;
23 | import java.util.Arrays;
24 | import java.util.Collections;
25 | import java.util.List;
26 | import java.util.Locale;
27 |
28 | import io.appflate.restmock.RESTMockServer;
29 | import okhttp3.mockwebserver.RecordedRequest;
30 |
31 | public final class RequestMatchers {
32 |
33 | private RequestMatchers() {
34 | throw new UnsupportedOperationException();
35 | }
36 |
37 | public static RequestMatcher pathContains(final String urlPart) {
38 | return new RequestMatcher("path contains: " + urlPart) {
39 |
40 | @Override
41 | protected boolean matchesSafely(RecordedRequest item) {
42 | return item.getPath().toLowerCase(Locale.US).contains(urlPart.toLowerCase(Locale.US));
43 | }
44 | };
45 | }
46 |
47 | public static RequestMatcher pathDoesNotContain(final String urlPart) {
48 | return new RequestMatcher("path does not contain: " + urlPart) {
49 |
50 | @Override
51 | protected boolean matchesSafely(RecordedRequest item) {
52 | return !item.getPath().toLowerCase(Locale.US).contains(urlPart.toLowerCase(Locale.US));
53 | }
54 | };
55 | }
56 |
57 | public static RequestMatcher pathMatchesRegex(final String pattern) {
58 | return new RequestMatcher("path matches with regex:" + pattern) {
59 |
60 | @Override
61 | protected boolean matchesSafely(RecordedRequest item) {
62 | return item.getPath().toLowerCase(Locale.US).matches(pattern);
63 | }
64 | };
65 | }
66 |
67 | public static RequestMatcher pathEndsWith(final String urlPart) {
68 | return new RequestMatcher("path ends with: " + urlPart) {
69 |
70 | @Override
71 | protected boolean matchesSafely(RecordedRequest item) {
72 | String urlPartWithoutEndingSlash = urlPart.replaceAll("/$", "");
73 | String itemPathWithoutEndingSlash = item.getPath().replaceAll("/$", "");
74 | return itemPathWithoutEndingSlash.toLowerCase(Locale.US).endsWith(urlPartWithoutEndingSlash.toLowerCase(Locale.US));
75 | }
76 | };
77 | }
78 |
79 | public static RequestMatcher pathEndsWithIgnoringQueryParams(final String endOfUrlPath) {
80 | return new RequestMatcher("path ends with: ${endOfUrlPath}") {
81 |
82 | protected boolean matchesSafely(RecordedRequest item) {
83 | String endOfPathSanitized = sanitizePath(endOfUrlPath);
84 | String recordedPathSanitized = sanitizePath(item.getPath());
85 | return recordedPathSanitized.endsWith(endOfPathSanitized);
86 | }
87 | };
88 | }
89 |
90 | private static String sanitizePath(String path) {
91 | try {
92 | return new URL("http", "localhost", path).getPath().replaceAll("/$", "");
93 | } catch (MalformedURLException e) {
94 | throw new RuntimeException(e);
95 | }
96 | }
97 |
98 | /**
99 | * Checks whether matched request's path starts with given string.
100 | * path is a part of the url after the server's url. Example:
101 | * {@code pathStartsWith("login")} would match {@code https://localhost:4583/login}
102 | *
103 | * @param urlPart desired beginning of the path we want to match with
104 | * @return A new {@link RequestMatcher} object that will match {@link RecordedRequest} if it's
105 | * path starts with given urlPart
106 | */
107 | public static RequestMatcher pathStartsWith(final String urlPart) {
108 | return new RequestMatcher("path starts with: " + urlPart) {
109 |
110 | @Override
111 | protected boolean matchesSafely(RecordedRequest item) {
112 | return item.getPath().toLowerCase(Locale.US).startsWith(urlPart.toLowerCase(Locale.US));
113 | }
114 | };
115 | }
116 |
117 | /**
118 | * Checks whether matched request contains non-null headers with given names
119 | *
120 | * @param headerNames names of headers to match
121 | * @return A new {@link RequestMatcher} object that will match {@link RecordedRequest} if it
122 | * contains specified header names
123 | */
124 | public static RequestMatcher hasHeaderNames(final String... headerNames) {
125 | return new RequestMatcher("has headers: " + Arrays.toString(headerNames)) {
126 |
127 | @Override
128 | protected boolean matchesSafely(RecordedRequest item) {
129 | if (headerNames == null) {
130 | throw new IllegalArgumentException("You did not specify any header names");
131 | }
132 | if (headerNames.length > 0 && item.getHeaders() == null) {
133 | return false;
134 | }
135 | for (String header : headerNames) {
136 | if (item.getHeader(header) == null) {
137 | return false;
138 | }
139 | }
140 | return true;
141 | }
142 | };
143 | }
144 |
145 | /**
146 | * Creates a {@link RequestMatcher} object for determining if a {@link RecordedRequest} contains
147 | * any query parameters.
148 | *
149 | * @return A new {@link RequestMatcher} object that will match a {@link RecordedRequest} if it
150 | * contains query parameters in its path.
151 | */
152 | public static RequestMatcher hasQueryParameters() {
153 | return new RequestMatcher("matched query parameters") {
154 |
155 | @Override
156 | protected boolean matchesSafely(RecordedRequest item) {
157 | try {
158 | URL mockUrl = new URL("http", "localhost", item.getPath());
159 | List queryParams = RestMockUtils.splitQuery(mockUrl);
160 | return queryParams.size() > 0;
161 | } catch (MalformedURLException e) {
162 | return false;
163 | } catch (UnsupportedEncodingException e) {
164 | return false;
165 | }
166 | }
167 | };
168 | }
169 |
170 | /**
171 | * Creates a {@link RequestMatcher} object for determining if a {@link RecordedRequest} contains
172 | * a specific set of query parameters names.
173 | *
174 | * @param expectedParamsNames A set of {@link String} objects to be matched.
175 | * @return A new {@link RequestMatcher} object that will match a {@link RecordedRequest} if it
176 | * contains the specified query parameters names in its path.
177 | */
178 | public static RequestMatcher hasQueryParameterNames(final String... expectedParamsNames) {
179 | return new RequestMatcher("matched query parameters names") {
180 |
181 | @Override
182 | protected boolean matchesSafely(RecordedRequest item) {
183 | try {
184 | URL mockUrl = new URL("http", "localhost", item.getPath());
185 | List actualParams = RestMockUtils.splitQuery(mockUrl);
186 | if (actualParams.size() == 0 || expectedParamsNames.length == 0) {
187 | return false;
188 | }
189 |
190 | List expectedParamNamesList = varArgToList(expectedParamsNames);
191 |
192 | int matchedParams = 0;
193 | for (QueryParam actualParam : actualParams) {
194 | if (expectedParamNamesList.contains(actualParam.getKey())) {
195 | matchedParams++;
196 | }
197 | }
198 |
199 | return matchedParams == expectedParamsNames.length;
200 |
201 | } catch (MalformedURLException e) {
202 | RESTMockServer.getLogger().error("URL appears to be malformed with path: " + item.getPath());
203 | return false;
204 | } catch (UnsupportedEncodingException e) {
205 | return false;
206 | }
207 | }
208 | };
209 | }
210 |
211 | /**
212 | * Creates a {@link RequestMatcher} object for determining if a {@link RecordedRequest} contains
213 | * a specific set of query parameters.
214 | *
215 | * @param expectedParams A set of {@link QueryParam} objects to be matched.
216 | * @return A new {@link RequestMatcher} object that will match a {@link RecordedRequest} if it
217 | * contains the specified query parameters in its path. Note that this
218 | * {@link RequestMatcher} only matches the exact specification of query parameters. That
219 | * is, if any of the key/value pairs don't match, or if the number of expected parameters
220 | * doesn't match the number of actual parameters, this {@link RequestMatcher} will
221 | * return false.
222 | */
223 | public static RequestMatcher hasExactQueryParameters(final QueryParam... expectedParams) {
224 | return new RequestMatcher("matched query parameters") {
225 |
226 | @Override
227 | protected boolean matchesSafely(RecordedRequest item) {
228 | try {
229 | URL mockUrl = new URL("http", "localhost", item.getPath());
230 | List actualParams = RestMockUtils.splitQuery(mockUrl);
231 | if (actualParams.size() == 0 || actualParams.size() != expectedParams.length) {
232 | return false;
233 | }
234 |
235 | for (QueryParam param : expectedParams) {
236 | if (!actualParams.contains(param)) {
237 | return false;
238 | }
239 | }
240 | return true;
241 | } catch (MalformedURLException e) {
242 | RESTMockServer.getLogger().error("URL appears to be malformed with path: " + item.getPath());
243 | return false;
244 | } catch (UnsupportedEncodingException e) {
245 | return false;
246 | }
247 | }
248 | };
249 | }
250 |
251 | public static RequestMatcher httpMethodIs(final String method) {
252 | return new RequestMatcher("HTTP method is: " + method) {
253 |
254 | @Override
255 | protected boolean matchesSafely(final RecordedRequest item) {
256 | return item.getMethod().equalsIgnoreCase(method);
257 | }
258 | };
259 | }
260 |
261 | public static RequestMatcher isGET() {
262 | return httpMethodIs("GET");
263 | }
264 |
265 | public static RequestMatcher isPOST() {
266 | return httpMethodIs("POST");
267 | }
268 |
269 | public static RequestMatcher isPATCH() {
270 | return httpMethodIs("PATCH");
271 | }
272 |
273 | public static RequestMatcher isDELETE() {
274 | return httpMethodIs("DELETE");
275 | }
276 |
277 | public static RequestMatcher isPUT() {
278 | return httpMethodIs("PUT");
279 | }
280 |
281 | public static RequestMatcher isHEAD() {
282 | return httpMethodIs("HEAD");
283 | }
284 |
285 | private static List varArgToList(String... args) {
286 | List strings = new ArrayList<>(args.length);
287 | Collections.addAll(strings, args);
288 | return strings;
289 | }
290 | }
291 |
--------------------------------------------------------------------------------
/core/src/main/java/io/appflate/restmock/utils/RestMockUtils.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Appflate.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.appflate.restmock.utils;
18 |
19 | import java.io.UnsupportedEncodingException;
20 | import java.net.URL;
21 | import java.net.URLDecoder;
22 | import java.util.Collections;
23 | import java.util.LinkedHashMap;
24 | import java.util.LinkedList;
25 | import java.util.List;
26 | import java.util.Map;
27 |
28 | import io.appflate.restmock.RESTMockFileParser;
29 | import okhttp3.mockwebserver.MockResponse;
30 |
31 | public final class RestMockUtils {
32 |
33 | public static MockResponse createResponseFromFile(RESTMockFileParser RESTMockFileParser, String jsonFilePath,
34 | int responseCode) throws Exception {
35 | String fileContents = RESTMockFileParser.readJsonFile(jsonFilePath);
36 | return new MockResponse().setResponseCode(responseCode).setBody(fileContents);
37 | }
38 |
39 | /**
40 | * Extract query parameters from a {@link URL}.
41 | *
42 | * @param url The {@link URL} to retrieve query parameters for.
43 | * @return A {@link List} of {@link QueryParam} objects. Each parameter has one key, and zero or
44 | * more values.
45 | * @throws UnsupportedEncodingException If unable to decode from UTF-8. This should never happen.
46 | */
47 | public static List splitQuery(URL url) throws UnsupportedEncodingException {
48 | final Map> queryPairs = new LinkedHashMap<>();
49 |
50 | String query = url.getQuery();
51 | if (query == null || query.trim().length() == 0) {
52 | return Collections.emptyList();
53 | }
54 |
55 | final String[] pairs = query.split("&");
56 | for (String pair : pairs) {
57 | final int idx = pair.indexOf("=");
58 | final String key = idx > 0 ? URLDecoder.decode(pair.substring(0, idx), "UTF-8") : pair;
59 | List valueList = new LinkedList<>();
60 |
61 | if (queryPairs.containsKey(key)) {
62 | valueList = queryPairs.get(key);
63 | }
64 |
65 | final String value = idx > 0 && pair.length() > idx + 1 ? URLDecoder.decode(pair.substring(idx + 1), "UTF-8") : null;
66 | valueList.add(value);
67 |
68 | queryPairs.put(key, valueList);
69 | }
70 |
71 | List finalParamList = new LinkedList<>();
72 | for (Map.Entry> entry : queryPairs.entrySet()) {
73 | QueryParam nextFinalParam = new QueryParam(entry.getKey(), entry.getValue());
74 | finalParamList.add(nextFinalParam);
75 | }
76 |
77 | return finalParamList;
78 | }
79 |
80 | private RestMockUtils() {
81 | throw new UnsupportedOperationException("(╯‵Д′)╯ PLEASE STAHP!");
82 |
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/core/src/test/java/io/appflate/restmock/QueryParamTest.java:
--------------------------------------------------------------------------------
1 | package io.appflate.restmock;
2 |
3 | import org.junit.Test;
4 |
5 | import java.net.URL;
6 | import java.util.List;
7 |
8 | import io.appflate.restmock.utils.QueryParam;
9 | import io.appflate.restmock.utils.RestMockUtils;
10 |
11 | import static junit.framework.TestCase.assertTrue;
12 | import static org.junit.Assert.assertEquals;
13 |
14 | public class QueryParamTest {
15 |
16 | @Test
17 | public void testBasicQueryParamSplit() throws Exception {
18 | URL url = new URL("https://www.jwir3.com/someRequest?flag=true&session_length=2");
19 |
20 | List params = RestMockUtils.splitQuery(url);
21 |
22 | QueryParam expectedParam1 = new QueryParam("flag", "true");
23 | QueryParam expectedParam2 = new QueryParam("session_length", "2");
24 |
25 | assertEquals(2, params.size());
26 | assertTrue(params.contains(expectedParam1));
27 | assertTrue(params.contains(expectedParam2));
28 | }
29 |
30 | @Test
31 | public void testMultipleValueQueryParamSplit() throws Exception {
32 | URL url = new URL("https://www.jwir3.com/someRequest?user_id=1&user_id=2");
33 |
34 | List params = RestMockUtils.splitQuery(url);
35 |
36 | QueryParam expectedParam1 = new QueryParam("user_id", "2", "1");
37 |
38 | assertEquals(1, params.size());
39 | assertTrue(params.contains(expectedParam1));
40 | }
41 |
42 | @Test
43 | public void testNoQueryParams() throws Exception {
44 | URL url = new URL("https://www.jwir3.com/someRequest");
45 |
46 | List params = RestMockUtils.splitQuery(url);
47 |
48 | assertEquals(0, params.size());
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/core/src/test/java/io/appflate/restmock/RESTMockServerTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Appflate.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.appflate.restmock;
18 |
19 | import io.appflate.restmock.utils.TestUtils;
20 | import java.io.IOException;
21 | import java.util.AbstractMap;
22 | import java.util.Arrays;
23 | import java.util.Collection;
24 | import okhttp3.Response;
25 | import okhttp3.mockwebserver.MockResponse;
26 | import okhttp3.mockwebserver.RecordedRequest;
27 | import org.junit.After;
28 | import org.junit.Before;
29 | import org.junit.Test;
30 | import org.junit.runner.RunWith;
31 | import org.junit.runners.Parameterized;
32 |
33 | import static io.appflate.restmock.utils.RequestMatchers.pathEndsWith;
34 | import static junit.framework.TestCase.assertEquals;
35 | import static junit.framework.TestCase.assertNotNull;
36 | import static org.mockito.Mockito.mock;
37 | import static org.mockito.Mockito.never;
38 | import static org.mockito.Mockito.spy;
39 | import static org.mockito.Mockito.verify;
40 |
41 | @RunWith(Parameterized.class)
42 | public class RESTMockServerTest {
43 |
44 | static RESTMockFileParser fileParser;
45 | private final boolean useHttps;
46 |
47 |
48 | @Parameterized.Parameters(name = "useHttps={0}")
49 | public static Collection