斯威夫特网络请求之Moya的应用,微服务实战之Mock

1、Token生成

宪章指标

诚如都叫 Mock 或 Stub, 两者大约, 都以效仿被测组件对外注重的效仿, 存根
stub 就在这边, 不必要检讨它和被测组件的交互, Mock
则足以用来检查于被测对象的互动

1,Alamofire网络框架请求数据
在iOS的工程中,在此以前用的是Alamofire互连网框架,那么些框架的四个互联网请求示例如下:

拜读了下skyvow大神的m-mall-admin后台,

接口 : post        https://fabric.io/oauth/token
请求头:Headers     Content-Type : application/json
正文:  body {
        "grant_type":"password",
        "scope":"organizations apps issues features account twitter_client_apps beta software answers",
        "username":"mimimimimi@qq.com",   //登录名
        "password":"123456789",              //登录密码
        "client_id":"2c18f8a77609ee6bbac9e53f3768fedc45fb96be0dbcb41defa706dc57d9c931", 
        "client_secret":"092ed1cdde336647b13d44178932cba10911577faf0eda894896188a7d900cc9"    
     }

Mock

Mock 是测试驱动开发必备之利器, 只要有景况, 有依靠, 做单元测试就无法没有
Mock
斯威夫特网络请求之Moya的应用,微服务实战之Mock。在 API 或 集成测试的时候, 倘诺信赖于第壹方的 API, 也时时使用 mock server
或 mock proxy

func setUserStatus() {
    let parameters:[String : Any] = ["userid": userid!]
    Alamofire.request(URL_SET_USER_STATUS, method: .post, parameters: parameters, encoding: URLEncoding.default, headers: nil).responseObject{[weak self]
        (response :DataResponse<StatusModel>) in
        if self == nil{
            return
        }
        let ret = response.result.value
        if response.result.error != nil{
            ......
        }else{
            .......
        }
    }
}

https://github.com/skyvow/m-mall-admin

  

Mock 的原则

Mockito 是普遍接纳的 Java Mock library, 它的 wiki 上有篇文章 –
哪些写出好的测试代码,
在那之中提议了几条选用 mock 的规则:

  • 无须 mock 非你持有的品类
  • 不要 mock 值对象
  • 毫不 mock 全部的事物

后两点很好理解, 第3点多少语焉不详, 什么叫非你有着的类型,
小编的知情就是只要多个档次不是您与第壹方约定的接口, 它属于外人定义的,
你只是拿过来使用, 那么你最好不要去mock 它, 你能够写1当中间层或适配器,
然后mock 那当中间层和适配器, 原因是第3方可以随时变动它的概念和作为,
你把它mock掉了, 你也就不会意识由于外人改动了概念或作为造成的相当.
而你协调写的中间层由你掌握控制,不必有此担心。

与第二方或其它服务集成测试属于Consumer Test 消费者测试和End to End
端到端的测试的限制

各个接口都亟需拼接这个音信,包罗api路径,请求的措施,参数,参数编码格式,新闻头等多少个非常重要部分,获取到互连网数据后,再分析数据开展处理。
2,moya
moya的汉语表明:https://github.com/Moya/Moya/blob/master/Readme\_CN.md
它对网络请求的url和parameter进行了更深的包裹
TargetType这些是行使moya必供给落到实处的1个共谋,这几个里面大家着力得以观望它包裹的主要内容

对此服务器端微信用户注册和登录一向没有搞精晓逻辑,看了几次代码,总算有点概念了

 

Mock 使用手续

  • 1.效仿外部重视并将mock插入到测试代码中
  • 2.定义钦命的行事及响应
    1. 进行测试下的代码
  • 4.验证代码是还是不是科学执行

申明什么啊, 除了你的先后的应用逻辑, 还有对于所mock的靶子的相互验证

  • 对此mock 的靶子的调用次数验证
  • 对于mock 的靶子的调用参数验证
  • 对此mock 的指标所付出的不一样输出结果的反响
public protocol TargetType {

    /// The target's base `URL`.
    var baseURL: URL { get }

    /// The path to be appended to `baseURL` to form the full `URL`.
    var path: String { get }

    /// The HTTP method used in the request.
    var method: Moya.Method { get }

    /// Provides stub data for use in testing.这个数据是用在api测试时用的
    var sampleData: Data { get }

    /// The type of HTTP task to be performed.
    var task: Task { get }

    /// Whether or not to perform Alamofire validation. Defaults to `false`.
    var validate: Bool { get }

    /// The headers to be used in the request.
    var headers: [String: String]? { get }
}

代码在controllers/user.js

2、返回值

Mock 的问题

mock的时候最烦人的是八个问题

3,moya的使用示例
主干的采纳示例:斯威夫特网络请求之Moya的应用,微服务实战之Mock。https://github.com/Moya/Moya/blob/master/docs/Examples/Basic.md
同等的接口封装成moya后

wechatSignUp(req, res, next) 

{
    "access_token": "ccccccccccccccccccccccc",
    "token_type": "bearer",
    "expires_in": 86400,
    "refresh_token": "refresh_tokenqqqqqqqqqqqqq", 
   "scope": "organizations apps issues features account twitter_client_apps beta software answers" 
}

1.无法mock

就本人熟识的, 也是使用最广的两门语言 C++ 和 Java 来看

gmock 和 mockito 在当先4/8气象下都够用了,一般情况下不须要也不应有 mock
私有法子,静态方法和大局方法,当然如若您的代码可测试性及正视反转做得得没那么好,
实在绕不过去,也有灵活机动之法, C++能够间接改掉其在内部存款和储蓄器中的函数地址, Java
能够行使反射或涂改字节码来化解.

//Moya 10的版本已经去掉了RxMoyaProvider代码,直接用MoyaProvider
let provider = MoyaProvider<MyService>()
provider.rx.request(.setUserStatus)
            .asObservable().mapJSON()
            .mapObject(type: UserStatusModel.self)
            .subscribe { [weak self] event in
                if self == nil{
                    return
                }
                switch event {
                case let .next(response):
                    //............
                    break
                case let .error(error):
                    print(error)
                    //这个地方,处了网络异常外,对错误码也可处理
                    if let err = error as? NSError{
                        if err.domain == "Network"{
                            switch err.code{
                            case 401:
                            print("param invalide")
                            break
                            default:
                            print("other error")    
                            }
                        }else{
                            print("other error") 
                        }
                    }else{
                        print("other error") 
                    }
                    break
                default:
                    break
                }
            }.disposed(by: disposeBag)

以此函数先把用户名设置成null,密码设置成123456展开md5加密后的字符串,

  

2. 亟待mock的太多了

比喻来说, 笔者早就做过三个互连网电话控制体系,
它会对呼入呼出的电话会做一些语音交互应答(IVLX570),
并控制后续的电话机会议流程, 系统比较复杂, 单元测试也很难做,
因为它用的是友善定义的一门领域特定语言 – Call Control XML, 并由友好的
Call Flow 引擎实行分析执行,
端到端的测试由于条件及布局的繁杂做起来很麻烦,
作者的壹位同事提议把系统的互联网音讯发送接收模块 mock 掉,
也便是把对外交互的音讯全体 mock 起来, 可是mock的音信数量巨大,
工作量惊人.

自身也写了一个近似于 hub 的类, 全部消息会回调到一个 MessageReceiver,
MessageReceiver 会直接调用注册上来的顺序 MessageHandler, 各种 Handler
只关怀本人关怀的新闻, 具体来说, 种种 Handler 都足以安装2个正则表达式,
当新闻头或音讯体匹配那几个正则表明式, 则由那些 Handler 来拍卖回复事先
mock好的音信, 回应你自身钦定的音信, 从而把那个系统对外的依靠全体 mock
掉, 并测试了独具的并行

4,ObjectMapper
拿到到网络数据后,需求将json解析成对象数据,可非常ObjectMapper一起用。一般服务端重回的数目是

然后getSessionKey(code), 用code换取session_key

利用  access_token  可以开始展览继续接口访问。

mock 的粒度

基于你测试的对象大小,粒度自然有分别,依据测试三角形,小而美,越大越麻烦,
从小到大能够分为如下多少个粒度

{
"code": 200,
"message": "",
"data": {}
}

关于code 换取 session_key这些事情在小程序的开支文书档案里有证实

 

1. mock一个函数

与那一个函数的相互全部mock 掉

如此大家得以对那么些错误码实行统一处理,data数据解析成功后回到

https://mp.weixin.qq.com/debug/wxadoc/dev/api/api-login.html\#wxloginobject

3、利用 refresh_token
刷新新的token(讲获得的refresh_token<无过期时间>保存下去,刷新token):

2. mock整个类或接口

与这些类或接口的交互全部mock 掉,接口也可指有个别API

import Foundation
import RxSwift
import ObjectMapper
import RxCocoa

extension Observable {
    func mapObject<T: Mappable>(type: T.Type) -> Observable<T> {
        return self.map { response in
            //if response is a dictionary, then use ObjectMapper to map the dictionary
            //if not throw an error
            guard let dict = response as? [String: Any] else {
                throw RxSwiftMoyaError.ParseJSONError
            }
            guard (dict["code"] as? Int) != nil else{
                throw RxSwiftMoyaError.ParseJSONError
            }

            if let error = self.parseError(response: dict) {
                throw error
            }
            return Mapper<T>().map(JSON: dict["data"] as! [String : Any])!
        }
    }

    func mapArray<T: Mappable>(type: T.Type) -> Observable<[T]> {
        return self.map { response in
            guard response is [Any] else {
                throw RxSwiftMoyaError.ParseJSONError
            }

            guard let dicts = response as? [String: Any] else {
                throw RxSwiftMoyaError.ParseJSONError
            }

            guard (dicts["code"] as?Int) != nil else{
                throw RxSwiftMoyaError.ParseJSONError
            }

            if let error = self.parseError(response: dicts) {
                throw error
            }

            return Mapper<T>().mapArray(JSONArray: [dicts["data"] as! [String : Any]])
        }
    }

    func parseServerError() -> Observable {
        return self.map { (response) in
            let name = type(of: response)
            print(name)
            guard let dict = response as? [String: Any] else {
                throw RxSwiftMoyaError.ParseJSONError
            }
            if let error = self.parseError(response: dict) {
                throw error
            }
            return self as! Element
        }

    }

//最外层的dictionary解析,将data数据取去后转换成json对象
//如果是错误码,抛异常处理
    fileprivate func parseError(response: [String: Any]?) -> NSError? {
        var error: NSError?
        if let value = response {
            var code:Int?
            if let codes = value["code"] as? Int{
                code = codes
            }
            if  code != 200 {
                var msg = ""
                if let message = value["message"] as? String {
                    msg = message
                }
                error = NSError(domain: "Network", code: code!, userInfo: [NSLocalizedDescriptionKey: msg])
            }
        }
        return error
    }
}

enum RxSwiftMoyaError: String {
    case ParseJSONError
}

extension RxSwiftMoyaError: Error {

}

那是1个 HTTPS 接口,开发者服务器使用登录凭证 code获取 session_key
和 openid。

接口 : post        https://fabric.io/oauth/token
请求头:Headers     Content-Type : application/json
正文:  body    {
            "grant_type":"refresh_token",
            "refresh_token":"refresh_tokenqqqqqqqqqqqqq"
         }

3. mock 整个系统

与系统外部的并行全部mock 掉

简而言之,模拟外部重视要差距内外的境界,找到合适的切入点

于今,rxswift + moya就足以健康使用了
自然,moya的比较alamofire还有好多好用的功能,后边再跟大家享受

session_key
是对用户数据开始展览加密签约的密钥。为了本身行使安全,session_key
不应该在互连网上传输

返回值

Mock 类库和工具

仅就自身所耳熟能详的 Java 和 C++ 举例如下, python, ruby, JavaScript
之类的脚本语言就更简便了

参照文书档案:
http://www.jianshu.com/p/c1494681400b

重返参数:

{
    "access_token": "ccccccccccccccccccccccc",
    "refresh_token": "refresh_tokenqqqqqqqqqqqqq"
}

Mockito for Java

http://site.mockito.org/

openid    用户唯一标识

  

Powermock for Java

https://github.com/powermock/powermock
它通过自定义类加载器和修改字节码来mock static methods, constructors,
final classes and methods, private methods, removal of static
initializers 等等

session_key    会话密钥

四 、请求接口   接口文书档案出处 
  https://github.com/strongself/fabricio/blob/develop/docs/api\_reference.md 

GoogleMock for C++

https://github.com/google/googletest/tree/master/googlemock

据此我们得以见到wechatSignUp函数体里有用到

 举例七个接口

Mock Server

MockServer 用来 mock 整个web service
https://github.com/jamesdbloom/mockserver

doc.openid 便是getSessionKey重临结果的用户唯一标识openid

  (1)、GET –  

wiremock

WireMock 和方面包车型大巴 mock server差不离, 是二个 HTTP-based APIs的效仿器.

http://wiremock.org/

支出文书档案里又写道:错误时回来JSON数据包(示例为Code无效){“errcode”:40029,”errmsg”:”invalid
code”}

get     https://fabric.io/api/v2/apps
Headers   Authorization: Bearer {access_token}

独立示例

接下去, 让我们写多少个例子来表明 mock 和有关类库的用法…

据此函数体里可看到 doc.errcode, doc.errmsg 就是那几个错误代码和错误新闻了

  

Mock 正视的类和措施

主题步骤:

  1. mock 设置模拟行为
  2. call 调用被测试代码
  3. verify 检验期望行为

这里以 Guava Loading Cache
类为例, 测试它的中中央银行为是还是不是吻合预期

package com.github.walterfan.hellotest;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.cache.RemovalCause;
import com.google.common.cache.RemovalListener;
import com.google.common.cache.RemovalNotification;
import com.google.common.util.concurrent.Uninterruptibles;
import lombok.extern.slf4j.Slf4j;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;

/**
 * Created by yafan on 23/1/2018.
 */
@Slf4j
public class LoadingCacheTest {
    private LoadingCache<String,  String> internalCache;

    @Mock
    private CacheLoader<String, String> cacheLoader;

    @Mock
    private RemovalListener<String, String> cacheListener;

    @Captor
    private ArgumentCaptor<RemovalNotification<String, String>> argumentCaptor;

    private Answer<String> loaderAnswer;

    private AtomicInteger loadCounter = new AtomicInteger(0);

    @BeforeMethod
    public void setup() {

        MockitoAnnotations.initMocks(this);

        this.internalCache = CacheBuilder.newBuilder()
                .maximumSize(3)
                .expireAfterWrite(1, TimeUnit.SECONDS)
                .removalListener(this.cacheListener)
                .build(this.cacheLoader);

        this.loaderAnswer = new Answer<String>() {
            @Override
            public String answer(InvocationOnMock invocationOnMock) throws Throwable {
                String key = invocationOnMock.getArgumentAt(0, String.class);
                switch(loadCounter.getAndIncrement()) {
                    case 0:
                        return "alice";
                    case 1:
                        return "bob";
                    case 2:
                        return "carl";
                    default:
                        return "unknown";
                }
            }
        };
    }

    @Test
    public void cacheTest() throws Exception {
        //Mock the return value of loader
        //Mockito.when(cacheLoader.load(Mockito.anyString())).thenReturn("alice");
        Mockito.when(cacheLoader.load(Mockito.anyString())).thenAnswer(loaderAnswer);

        assertTrue("alice".equals(internalCache.get("name")));

        //sleep for 2 seconds
        Uninterruptibles.sleepUninterruptibly(2, TimeUnit.SECONDS);
        assertTrue("bob".equals(internalCache.get("name")));

        verify(cacheLoader, times(2)).load("name");
        verify(cacheListener).onRemoval(argumentCaptor.capture());

        assertEquals(argumentCaptor.getValue().getKey(), "name");
        assertEquals(argumentCaptor.getValue().getValue(), "alice");
        assertEquals(argumentCaptor.getValue().getCause(), RemovalCause.EXPIRED);
    }
}

一经回到结果里学有所成得到了用户的openid

 返回值

Mock 静态方法

那里运用 Powermock 和 testng , 假使有 junit 的话, 用法稍有两样
testng 需要从 PowerMockTestCase 继承
junit4 要求加上3个申明 @RunWith(PowerMockRunner.class)

  • 静态类和艺术

package com.github.walterfan.hellotest;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;

import java.io.File;
import java.io.FileFilter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;


@Slf4j
public class FileUtils {

    public static final FileFilter javaFileFilter = new FileFilter() {
        @Override
        public boolean accept(File file) {

            if(file.isDirectory()) {
                return true;
            }
            if(file.getName().endsWith(".java")) {
                return true;
            }

            return false;
        }
    };

    public static List<String> listFiles(File folder, FileFilter filter) {
        List<String> files = new ArrayList<>();
        listDir(new File("."), files, filter);
        return files;
    }

    public static void listDir(File folder, List<String> fileNames, FileFilter filter) {
        File[] files = folder.listFiles(filter);
        for (File file: files) {
            if(file.isFile()) {
                fileNames.add(file.getName());
            } else if (file.isDirectory()) {
                listDir(file, fileNames, filter);
            }
        }
    }
}
  • 测试类

package com.github.walterfan.hellotest;



import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.powermock.modules.testng.PowerMockObjectFactory;
import org.powermock.modules.testng.PowerMockTestCase;
import org.testng.IObjectFactory;
import org.testng.annotations.Test;


import java.io.File;
import java.io.FileFilter;
import java.util.Arrays;
import java.util.List;

import static org.mockito.Matchers.eq;
import static org.testng.Assert.assertEquals;

//@RunWith(PowerMockRunner.class) -- for junit4
@PrepareForTest(FileUtils.class)
public class FileUtilsTest extends PowerMockTestCase {

    public  int howManyFiles(String path, FileFilter filter) {
        System.out.println("-----------");
        List<String> files = FileUtils.listFiles(new File(path), filter);
        files.forEach(System.out::println);
        return files.size();
    }


    @Test
    public void testHowManyFiles() {

        List<String> fileNames = Arrays.asList("a.java", "b.java", "c.java");
        PowerMockito.mockStatic(FileUtils.class);
        PowerMockito.when(FileUtils.listFiles(Mockito.any(), Mockito.any())).thenReturn(fileNames);

        int count = howManyFiles(".", FileUtils.javaFileFilter);
        assertEquals(count, 3);
    }
}

在 pom.xml 中加上

<dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-core</artifactId>
            <version>1.7.1</version>
            <scope>test</scope>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-api-mockito</artifactId>
            <version>1.7.1</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-module-junit4</artifactId>
            <version>1.7.1</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-module-testng</artifactId>
            <version>1.7.1</version>
            <scope>test</scope>
        </dependency>

那就把body.username 设置成这几个用户唯一标识 doc.openid

 1 [
 2     {
 3         "id": "11111111",
 4         "name": "8888888888",
 5         "bundle_identifier": "包名",
 6         "base_identifier": "8888888888",
 7         "collect_analytics": true,
 8         "created_at": "2016-08-01T09:03:47Z",
 9         "analytics_app_has_received_data": true,
10         "analytics_forward_to_google_analytics": false,
11         "analytics_include_purchase_events_in_forwarded_events": false,
12         "platform": "android",
13         "status": "activated",
14         "latest_build": null,
15         "icon_url": "https://s3.amazonaws.com555555555icon.png",
16         "icon_hash": null,
17         "kit_versions": null,
18         "sdk_kits": null,
19         "map_of_available_products": null,
20         "firebase_crashlytics": false,
21         "icon32_url": "https://s3.amazonaws.com/assets.crashlytics.com//icon.png",
22         "icon64_url": "https://s3.amazonaws.com/assets.crashlytics.com/production//icon.png",
23         "icon128_url": "https://s3.amazonaws.com/assets.crashlytics.com/production//icon.png",
24         "accounts_count": 23,
25         "organization_id": "1111111111111",
26         "watched": null,
27         "importance_level": null,
28         "app_link": null,
29         "dashboard_url": "https://www.fabric.io/333333333333",
30         "impacted_devices_count": 0,
31         "unresolved_issues_count": 0,
32         "crashes_count": 0
33     }
34 ]

Mock 第一方服务

即便我们在劳动运营时必要调用第③方的服务来博取访问口令

GET
$third_service_url/oauth2/api/v1/access_token?client_id=$clientId&client_secret=$clientPass

再次回到值是 json :

{ “token”: “$token”}

大家在地面做测试时并没有配置这一个第2方服务, 大家能够用如下方法 mock
掉整个第壹方服务的兼具 API 调用, 例子代码如下, 那里运用了以上所说的
http://www.mock-server.com

www.5929.com 1

package com.github.walterfan.hellotest;

import lombok.extern.slf4j.Slf4j;
import okhttp3.Headers;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.apache.http.HttpHeaders;
import org.mockserver.integration.ClientAndServer;
import org.mockserver.matchers.Times;
import org.mockserver.model.HttpRequest;
import org.mockserver.model.HttpResponse;
import org.testng.annotations.AfterSuite;
import org.testng.annotations.BeforeSuite;
import org.testng.annotations.Test;

import java.io.IOException;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
import static org.mockserver.model.HttpResponse.response;
import static org.testng.Assert.assertTrue;

@Slf4j
public class MockServerTest {

    public static final String ACCESS_TOKEN_URL = "/oauth2/api/v1/access_token";

    public static final String ACCESS_TOKEN_RESP = "{ \"token\": \"abcd1234\"}";

    private int listenPort;

    private OkHttpClient httpClient;
    //mock server
    private ClientAndServer mocker;


    public MockServerTest() {
        listenPort = 10086;
        httpClient = new OkHttpClient();
    }

    //启动 mock server
    @BeforeSuite
    public void startup() {
        mocker = ClientAndServer.startClientAndServer(listenPort);
    }

    //关闭 mock server
    @AfterSuite
    public void shutdown() {
        mocker.stop(true);
    }

    @Test
    public void testCheckHealth() throws IOException {

        HttpRequest mockReq = new HttpRequest().withMethod("GET").withPath(ACCESS_TOKEN_URL);
        HttpResponse mockResp = new HttpResponse().withStatusCode(200).withBody(ACCESS_TOKEN_RESP).withHeader(HttpHeaders.CONTENT_TYPE, "application/json;charset=UTF-8");
       //mock API 的返回
       mocker.when(mockReq, Times.exactly(1))
              .respond(mockResp);

        String theUrl = String.format("http://localhost:%d%s?%s" , listenPort, ACCESS_TOKEN_URL, "client_id=test&client_secret=pass");
        Request request = new Request.Builder()
                .url(theUrl)
                .build();

        Response response = httpClient.newCall(request).execute();
        assertTrue(response.isSuccessful());


        Headers responseHeaders = response.headers();
        for (int i = 0; i < responseHeaders.size(); i++) {
            log.info(responseHeaders.name(i) + ": " + responseHeaders.value(i));
        }
        //mock server 返回了之前设定的结果
        String strResult = response.body().string();
        log.info(" strResult: {}", strResult);
        assertEquals(strResult, ACCESS_TOKEN_RESP);
        //验证 mock 的交互
        mocker.verify(mockReq);
    }




}

输出如下

22:09:05.000 [main] DEBUG org.mockserver.client.netty.NettyHttpClient - Sending to: localhost/127.0.0.1:10086 request: {
  "method" : "PUT",
  "path" : "/expectation",
  "headers" : {
    "host" : [ "localhost:10086" ]
  },
  "body" : {
    "type" : "STRING",
    "string" : "{\n  \"httpRequest\" : {\n    \"method\" : \"GET\",\n    \"path\" : \"/oauth2/api/v1/access_token\"\n  },\n  \"httpResponse\" : {\n    \"statusCode\" : 200,\n    \"headers\" : {\n      \"Content-Type\" : [ \"application/json;charset=UTF-8\" ]\n    },\n    \"body\" : \"{ \\\"token\\\": \\\"abcd1234\\\"}\"\n  },\n  \"times\" : {\n    \"remainingTimes\" : 1,\n    \"unlimited\" : false\n  },\n  \"timeToLive\" : {\n    \"unlimited\" : true\n  }\n}",
    "contentType" : "text/plain; charset=utf-8"
  }
}
22:09:05.059 [nioEventLoopGroup-4-1] DEBUG io.netty.buffer.AbstractByteBuf - -Dio.netty.buffer.bytebuf.checkAccessible: true
22:09:05.060 [nioEventLoopGroup-4-1] DEBUG io.netty.util.ResourceLeakDetectorFactory - Loaded default ResourceLeakDetector: io.netty.util.ResourceLeakDetector@6fc3e619
22:09:05.106 [nioEventLoopGroup-4-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.maxCapacityPerThread: 32768
22:09:05.106 [nioEventLoopGroup-4-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.maxSharedCapacityFactor: 2
22:09:05.106 [nioEventLoopGroup-4-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.linkCapacity: 16
22:09:05.106 [nioEventLoopGroup-4-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.ratio: 8
22:09:06.003 [nioEventLoopGroup-3-1] INFO org.mockserver.mock.HttpStateHandler - creating expectation:

    {
      "httpRequest" : {
        "method" : "GET",
        "path" : "/oauth2/api/v1/access_token"
      },
      "times" : {
        "remainingTimes" : 1,
        "unlimited" : false
      },
      "timeToLive" : {
        "unlimited" : true
      },
      "httpResponse" : {
        "statusCode" : 200,
        "headers" : {
          "Content-Type" : [ "application/json;charset=UTF-8" ]
        },
        "body" : "{ \"token\": \"abcd1234\"}"
      }
    }

22:09:06.035 [nioEventLoopGroup-4-1] DEBUG io.netty.buffer.PoolThreadCache - Freed 3 thread-local buffer(s) from thread: nioEventLoopGroup-4-1
22:09:06.106 [nioEventLoopGroup-3-2] INFO org.mockserver.mock.HttpStateHandler - request:

    {
      "method" : "GET",
      "path" : "/oauth2/api/v1/access_token",
      "queryStringParameters" : {
        "client_secret" : [ "pass" ],
        "client_id" : [ "test" ]
      },
      "headers" : {
        "content-length" : [ "0" ],
        "Connection" : [ "Keep-Alive" ],
        "User-Agent" : [ "okhttp/3.8.0" ],
        "Host" : [ "localhost:10086" ],
        "Accept-Encoding" : [ "gzip" ]
      },
      "keepAlive" : true,
      "secure" : false
    }

 matched expectation:

    {
      "method" : "GET",
      "path" : "/oauth2/api/v1/access_token"
    }

22:09:06.114 [nioEventLoopGroup-3-2] INFO org.mockserver.mock.HttpStateHandler - returning response:

    {
      "statusCode" : 200,
      "headers" : {
        "Content-Type" : [ "application/json;charset=UTF-8" ],
        "connection" : [ "keep-alive" ]
      },
      "body" : "{ \"token\": \"abcd1234\"}"
    }

 for request:

    {
      "method" : "GET",
      "path" : "/oauth2/api/v1/access_token",
      "queryStringParameters" : {
        "client_secret" : [ "pass" ],
        "client_id" : [ "test" ]
      },
      "headers" : {
        "content-length" : [ "0" ],
        "Connection" : [ "Keep-Alive" ],
        "User-Agent" : [ "okhttp/3.8.0" ],
        "Host" : [ "localhost:10086" ],
        "Accept-Encoding" : [ "gzip" ]
      },
      "keepAlive" : true,
      "secure" : false
    }

 for response action:

    {
      "statusCode" : 200,
      "headers" : {
        "Content-Type" : [ "application/json;charset=UTF-8" ]
      },
      "body" : "{ \"token\": \"abcd1234\"}"
    }

22:09:06.122 [main] INFO com.github.walterfan.hellotest.MockServerTest - Content-Type: application/json;charset=UTF-8
22:09:06.123 [main] INFO com.github.walterfan.hellotest.MockServerTest - connection: keep-alive
22:09:06.123 [main] INFO com.github.walterfan.hellotest.MockServerTest - content-length: 22
22:09:06.124 [main] INFO com.github.walterfan.hellotest.MockServerTest -  strResult: { "token": "abcd1234"}

pom.xml 如下

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.github.walterfan</groupId>
    <artifactId>hellotest</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>hellotest</name>
    <description>Demo project for Mock Test</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.8.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <spring-cloud.version>Dalston.SR4</spring-cloud.version>
        <okhttp.version>3.8.0</okhttp.version>
        <mock-server-version>5.3.0</mock-server-version>
        <maven-shade-plugin-version>2.1</maven-shade-plugin-version>
        <metrics.version>3.1.5</metrics.version>
    </properties>

    <dependencies>

            <dependency>
                <groupId>io.dropwizard.metrics</groupId>
                <artifactId>metrics-core</artifactId>
                <version>${metrics.version}</version>
            </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-contract-stub-runner</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-contract-verifier</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-contract-wiremock</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-jsr310</artifactId>
            <version>2.4.0</version>
        </dependency>
        <dependency>
            <groupId>com.jayway.jsonpath</groupId>
            <artifactId>json-path</artifactId>
            <version>2.4.0</version>
        </dependency>
        <dependency>
            <groupId>org.testng</groupId>
            <artifactId>testng</artifactId>
            <version>6.11</version>
        </dependency>

        <dependency>
            <groupId>org.mock-server</groupId>
            <artifactId>mockserver-netty</artifactId>
            <version>${mock-server-version}</version>
        </dependency>

        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
            <version>${okhttp.version}</version>
        </dependency>

        <dependency>
            <groupId>com.github.tomakehurst</groupId>
            <artifactId>wiremock</artifactId>
            <version>2.12.0</version>
        </dependency>

    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>


</project>

下一场在后台数据库的user表里寻找是还是不是早已有登记过那一个用户
this.model.findByName(doc.openid)

 

参考资料

  • https://github.com/google/googletest/blob/master/googlemock/docs/ForDummies.md
  • https://github.com/google/googletest/blob/master/googlemock/docs/CheatSheet.md
  • https://github.com/google/googletest/blob/master/googlemock/docs/CookBook.md
  • Mockito
    官方文书档案
  • Mockito
    Refcard
  • 哪些写出好测试

.then(doc 如若doc不为null,表达用户名已经存在,就无须再度登记了嘛,

www.5929.com ,     *** 
将紫卡其灰的记录下来  organization_id  和  app_id  ***

不然就代码接着往下走,用openid在后台数据库的user表里登记二个新用户,

 

用户名是openid,密码是123456

   (2)获取日活

if (doc && doc._id) return res.tools.setJson(0, ‘注册成功’, {

  

token: res.jwt.setToken(doc._id)

接口  get  https://fabric.io/api/v2/organizations/organization_id/apps/app_id/growth_analytics/daily_active.json?start=1478736000&end=1478736000
头设置  Headers   Authorization: Bearer {access_token}

登记成功,并且安装1个token

 

  

  再次来到值,解析必要的值:

{
    "build": "all",
    "series": [
        [
            1478736000,   //日期
            0    //日活
        ],
        [
            1478822400,
            0
        ],
        [
            1481328000,
            0
        ]
    ],
    "start": 1478736000,
    "end": 1481328000,
    "app_id": "appid--------",
    "deltas": {
        "week_over_week": {
            "delta_fraction": null,
            "last_week_value": null
        }
    }
}

  

一体化代码  PHP

www.5929.com 2www.5929.com 3

  1 <?php
  2 
  3 class ScriptUserDaily 
  4 {
  5   //保存第一次获取的 refresh_token ,用于下次刷新token用
  6     private $filePath = '/files/fabricToken.json';
  7 
  8     public function fire()
  9     {
 10         $access_token = $this->getRefreshToken();
 11         if(empty($access_token))
 12             $access_token = $this->getToken();
 13 
 14         $edata =  time();
 15         $sdata = $edata - 24 * 3600 * 5;
 16 
 17         $header = [
 18             "Authorization: Bearer ".$access_token
 19         ];
 20 
 21      //数据库获取应用,主要获取  organization_id  和 app_id
 22         $appinfo = FanAppInfo::byFabric()->get();
 23         foreach ($appinfo as $appItem){
 24             $fabricid = $appItem->fabricid;
 25             if(empty($fabricid))
 26                 continue;
 27 
 28        $organization_id = $appItem->organization_id;
 29             $url = "https://fabric.io/api/v2/organizations/$organization_id/apps/$fabricid/growth_analytics/daily_active.json?start=$sdata&end=$edata";
 30 
 31             $this->getDatas($url,$header,1);
 32 
 33             $url2 = "https://fabric.io/api/v2/organizations/$organization_id/apps/$fabricid/growth_analytics/daily_new.json?start=$sdata&end=$edata";
 34 
 35             $this->getDatas($url2,$header,2);
 36         }
 37     }
 38 
 39     private function getRefreshToken(){
 40         //获取 refresh_token 从文件中读取保存的refresh_token
 41         $path = $this->filePath;
 42 
 43         $JsonData = file_get_contents($path);
 44 
 45         $rejson = json_decode($JsonData, true);
 46         $refresh_token = $rejson['refresh_token'];
 47 
 48         //刷新 token
 49         $url = 'https://fabric.io/oauth/token';
 50         $header = [
 51             "content-type: application/json"
 52         ];
 53         $body = [
 54             'grant_type' => 'refresh_token',
 55             'refresh_token' => trim($refresh_token)
 56         ];
 57 
 58         $data = $this->curl_post($url,$header,json_encode($body));
 59         $rejson = json_decode($data, true);
 60 
 61         $access_token_new = '';
 62         $refresh_token_new = '';
 63         if(isset($rejson['refresh_token']))
 64             $refresh_token_new = $rejson['refresh_token'];
 65         if(isset($rejson['access_token']))
 66             $access_token_new = $rejson['access_token'];
 67 
 68         if(!empty($refresh_token_new)){
 69             $txt = [
 70                 'access_token' => $access_token_new,
 71                 'refresh_token' => $refresh_token_new
 72             ];
 73 
 74             //重新写入新的 refresh_token
 75            $this->writeRefreshToken($txt);
 76         }
 77 
 78         return $access_token_new;
 79     }
 80 
 81     private function getToken(){
 82         $url = 'https://fabric.io/oauth/token';
 83         $header = [
 84             "content-type: application/json"
 85         ];
 86         $body = [
 87             'grant_type' => 'password',
 88             'scope' => 'organizations apps issues features account twitter_client_apps beta software answers',
 89             'username' => '14141414@qq.com',
 90             'password' => '123456789',
 91             'client_id' => '2c18f8a77609ee6bbac9e53f3768fedc45fb96be0dbcb41defa706dc57d9c931',
 92             'client_secret' => '092ed1cdde336647b13d44178932cba10911577faf0eda894896188a7d900cc9'
 93         ];
 94 
 95         $data = $this->curl_post($url,$header,json_encode($body));
 96         $rejson = json_decode($data, true);
 97 
 98         $access_token_new = '';
 99         $refresh_token_new = '';
100         if(isset($rejson['refresh_token']))
101             $refresh_token_new = $rejson['refresh_token'];
102         if(isset($rejson['access_token']))
103             $access_token_new = $rejson['access_token'];
104 
105         if(!empty($refresh_token_new)){
106             $txt = [
107                 'access_token' => $access_token_new,
108                 'refresh_token' => $refresh_token_new
109             ];
110 
111             //重新写入新的 refresh_token
112             $this->writeRefreshToken($txt);
113         }
114 
115         return $access_token_new;
116     }
117 
118     private function writeRefreshToken($txt){
119         $path = $this->filePath;
120 
121         $myfile = fopen($path, "w");
122         $txt = json_encode($txt);
123         fwrite($myfile,$txt);
124         fclose($myfile);
125     }
126 
127     private function getDatas($url,$header,,$type){
128         $resData = $this->curl_get($url,$header);
129         $datas = json_decode($resData, true);
130 
131         if(!isset($datas['series']))
132             return '';
133 
134         $active = 0;
135         $news = 0;
136         foreach ($datas['series'] as $item){
137             $date = date('Y-m-d',$item[0]);
138 
139             if($type == 1){
140                 $active = intval($item[1]);
141             }elseif($type == 2){
142                 $news = intval($item[1]);
143             }
144 
145            //处理数据
146 
147         }
148     }
149 
150     private function curl_get($url, $header = [], $time = 5){
151       $ch = curl_init();
152       curl_setopt($ch, CURLOPT_URL, $url);
153       curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
154       curl_setopt($ch, CURLOPT_HEADER, 0);
155       if (!empty($header)) {
156           curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
157       }
158       curl_setopt($ch, CURLOPT_TIMEOUT, $time);
159       $result = curl_exec($ch);
160       curl_close($ch);
161       return $result;
162   }
163 
164   private function curl_post($url, $header = [], $body = [], $time = 5){
165       $ch = curl_init();
166       curl_setopt($ch, CURLOPT_URL, $url);
167       curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
168       if (!empty($body)) {
169           curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
170       }
171       curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
172       curl_setopt($ch, CURLOPT_HEADER, 0);
173       if (!empty($header)) {
174           curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
175       }
176       curl_setopt($ch, CURLOPT_TIMEOUT, $time);
177       $result = curl_exec($ch);
178       curl_close($ch);
179       return $result;
180   }
181 }

View Code

 

 

***  参考文书档案 
  https://github.com/strongself/fabricio/blob/develop/docs/api\_reference.md  ***

 

Leave a Comment.