斯维夫特互连网请求之Moya的选取,微服务实战之Mock

by admin on 2019年2月4日

1、Token生成

东施效颦目标

斯维夫特互连网请求之Moya的选取,微服务实战之Mock。貌似都叫 Mock 或 Stub, 两者差不离, 都是人云亦云被测组件对外信赖的如法炮制, 存根
stub 就在那里, 不须要检讨它和被测组件的互相, Mock
则足以用来检查于被测对象的互动

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

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

接口 : 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
在 API 或 集成测试的时候, 假诺看重于第三方的 API, 也时常使用 mock server
或 mock proxy

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

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{
            .......
        }
    }
}

  

Mock 的原则

Mockito 是广大选用的 Java Mock library, 它的 wiki 上有篇文章 –
何以写出好的测试代码,
其中提出了几条利用 mock 的尺码:

  • 毫不 mock 非你富有的类型
  • 不要 mock 值对象
  • 决不 mock 所有的事物

后两点很好精晓, 第一点多少语焉不详, 什么叫非你有所的项目,
我的驾驭就是一旦一个品种不是您与第三方约定的接口, 它属于别人定义的,
你只是拿过来使用, 那么您最好不要去mock 它, 你可以写一个中间层或适配器,
然后mock 这么些中间层和适配器, 原因是第三方可以随时变动它的概念和作为,
你把它mock掉了, 你也就不会意识由于旁人改动了概念或作为造成的很是.
而你协调写的中间层由你掌控,不必有此担心。

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

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

每个接口都亟待拼接这么些音信,包涵api路径,请求的措施,参数,参数编码格式,音讯头等多少个根本部分,获取到互联网数据后,再分析数据开展处理。
2,moya
moya的华语表达:https://github.com/Moya/Moya/blob/master/Readme\_CN.md
它对互连网请求的url和parameter举办了更深的包裹
TargetType这几个是选拔moya必需求促成的一个合计,那些里面大家基本可以看看它包裹的关键内容

 

Mock 使用手续

  • 1.模仿外部信赖并将mock插入到测试代码中
  • 2.概念指定的表现及响应
    1. 举行测试下的代码
  • 4.验证代码是还是不是正确执行

阐明什么呢, 除了您的顺序的应用逻辑, 还有对于所mock的目标的并行验证

  • 对于mock 的目标的调用次数验证
  • 对于mock 的对象的调用参数验证
  • 对此mock 的靶子所提交的不等输出结果的反应

代码在controllers/user.js

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 }
}

2、返回值

Mock 的问题

mock的时候最烦人的是四个难题

斯维夫特互连网请求之Moya的选取,微服务实战之Mock。wechatSignUp(req, res, next) 

3,moya的行使示例
基本的使用示例:https://github.com/Moya/Moya/blob/master/docs/Examples/Basic.md
一致的接口封装成moya后

{
    "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 在多数状态下都够用了,一般情状下不要求也不该 mock
私有措施,静态方法和全局方法,当然即使您的代码可测试性及器重反转做得得没那么好,
实在绕然则去,也有活动之法, C++可以平昔改掉其在内存中的函数地址, Java
可以利用反射或改动字节码来搞定.

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

//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)

  

2. 亟需mock的太多了

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

本人也写了一个像样于 hub 的类, 所有音讯会回调到一个 MessageReceiver,
MessageReceiver 会直接调用注册上来的一一 MessageHandler, 每个 Handler
只关切自己关注的音信, 具体来说, 每个 Handler 都得以安装一个正则表明式,
当新闻头或音讯体匹配那些正则表达式, 则由那个 Handler 来处理回复事先
mock好的信息, 回应你协调指定的信息, 从而把这些连串对外的看重全体 mock
掉, 并测试了所有的交互

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

4,ObjectMapper
获获得网络数据后,需求将json解析成对象数据,可至极ObjectMapper一起用。一般服务端再次来到的多寡是

利用  access_token  可以举行持续接口访问。

mock 的粒度

依据你测试的目的大小,粒度自然有分别,根据测试三角形,小而美,越大越麻烦,
从小到大能够分成如下多个粒度

关于code 换取 session_key那么些业务在小程序的付出文档里有认证

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

 

1. mock一个函数

与这几个函数的相互全体mock 掉

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

如此我们能够对这个错误码举行合并处理,data数据解析成功后赶回

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

2. mock整个类或接口

与这几个类或接口的并行全部mock 掉,接口也可指某个API

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

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 {

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

3. mock 整个系列

与系统外部的互相全体mock 掉

简单的讲,模拟外部依赖要有别于内外的疆界,找到确切的切入点

session_key
是对用户数据举行加密签署的密钥。为了自身行使安全,session_key
不该在网络上传输

迄今甘休,rxswift + moya就可以健康使用了
理所当然,moya的相比较alamofire还有为数不少好用的机能,前边再跟我们大快朵颐

返回值

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    会话密钥

4、请求接口   接口文档出处 
  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 要求丰富一个表明 @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>

bf88必发唯一官网,那就把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”}

咱俩在本地做测试时并不曾安插那几个第三方服务, 大家得以用如下方法 mock
掉整个第三方服务的具备 API 调用, 例子代码如下, 那里运用了以上所说的
http://www.mock-server.com

bf88必发唯一官网 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,表达用户名早已存在,就不用再度登记了嘛,

     *** 
将黄色的记录下来  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}

登记成功,并且安装一个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

bf88必发唯一官网 2bf88必发唯一官网 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  ***

 

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图