• 你可以创造一个东西,至于它怎样发展,那就不是你所能预期的了。

    Moco里面有这样一个特性,把一个目录下的所有文件挂到一个特定的URI下。其用法如下:

    server.mount("dir”, to(”/site”);
    (API)

    {
      "mount" :    {
          "dir" : "dir",
          "uri" : "/site"
        }
    }
    (JSON)

    这么做的初衷是简化一组文件的挂接过程。不过,出人意料的是,有人把这个功能用在了web前端的开发上。

    下面是一个简化过的moco配置文件:
    [
    {
        "mount" : {
            "dir" : "site",
            "uri" : "/"
        }
    },
    {
        "request" : {
            "uri" : "/api"
        },
        "response" : {
            "text" : "foo"
        }
    }
    ]
    (site.json)

    可以看到,我们把site目录挂到了web站点的根目录下,此外,还模拟了一个/api的服务,返回foo。然后,把jquery文件放到site目录下,并在里面写一个index.html:

    <html>
    <head>
      <script src="jquery-1.9.1.min.js"></script>
      <script>
      $(function() {
        $("button").click(function() {
          $.get("/api", function(data, textStatus) {
            $("p").text(data);
          })
        });
      });
      </script>
    </head>
    <body>
      <p>Hello,World</p>
      <button type="button">Click</button>
    </body>
    </html>
    (index.html)

    从这段html代码我们可以看出,当点击按钮时,它会请求到/api这个uri,然后将请求回来的内容更新上去。正如我们所见到的,实际上,没有一个真正/api服务,只是由moco模拟了一个服务而已。把它运行起来:

      java -jar moco-runner--standalone.jar -p 12306 site.json

    当我们用浏览器打开这个页面时,点击按钮,我们会看到文字改变了。

    通过这个简单的例子,我们便不难理解Moco对于前端开发上的作用了,它启动一个真正的web服务器,使我们正在编写的页面起作用,然后,使用真正的ajax调用去访问后台服务,而Moco会可以将不存在的服务模拟出来。

    这么做的好处在于,所有的动态交互部分都是真的。如果在项目前期,开发一个原型验证有效性,做出的包括CSS和JavaScript都是具有真正功能的,在正式开发开始时,可以直接移植到项目里面。这样,也就给了那些不熟悉后端的前端开发人员一个机会,做出具有真正效果的原型。

    Moco还能怎么用呢?我们拭目以待。

  • 你可以创造一个东西,至于它怎样发展,那就不是你所能预期的了。

    我写Moco的初衷是为了简化集成,设计API是我最主要的关注点。Moco Runner是一个顺手完成的部分,让Moco可以独立运行起来。但每个人的关注点是不同的,有人把Moco用了起来,但是用法同我最初的设想完全不一样。

    我知道的第一个Moco用户是我的一个同事,我在澳洲出差的那段时间给他介绍了Moco,他当时正要写一个iOS上的一个客户端。因为服务器端API尚未开发,更准确的说,连API应该是什么样子还没有人清楚。为了能够让他的iOS客户端能够顺利编写下去,他用Moco模拟了一个服务器,来什么请求,返回什么样的应答。于是,他高高兴兴地写起了他的客户端。在开发的过程中,他不断地调整着API的设计,因为只有通过实际的开发,他才知道自己真正想要的API是什么样子的。就这样,在服务器端代码还没有真正动手之前,他已经提供出一份真正满足他需要的API文档,剩下的就是服务器端照着这份API去实现了。

    公司内部正与立人图书馆展开合作,帮助他们开发一个图书管理的手机端应用。你猜对了,这个项目里Moco也起到了作用。手机端应用在服务器端尚未就绪的情况下便启动了,他们用Moco模拟了一个服务器,这样,手机端应用就可以顺利地开始开发了。Moco新增了一个功能,当配置文件修改时,自动重新加载,这个功能就是由这个项目的人提出的。

    从这两个例子里可以看到,在移动开发中,Moco起到了很大的作用:在服务器端开发完成之前,客户端通过Moco构建的模拟服务器就可以进行开发。

    把Moco和移动开发结合起来,这是我设计Moco时从未有过的想法,这也是创造一个东西的魅力所在,你无法预期它会朝着哪个方向发展。近来不断地将Moco介绍给更多的人,越来越多的新想法也涌现了出来。有人想把它与前端开发结合起来,有人想让它反过来支持客户端的模拟。

    我原以为Moco已经没什么好做的了,因为我想实现的东西都有了,现在看来,还停不下来。

  • 如你所见,Moco主要是通过配置模拟服务器端的行为。目前主要支持两种配置:请求(Request)和应答(Response)。简而言之,当请求是什么样时,返回怎样的应答。前面你已经见过最简单的例子了,不管请求什么都返回“foo”作为应答。

    请求(Request)

    • 内容

    有时,我们希望根据请求的内容返回相应的应答,我们可以这样配置:

    server.request(by("foo")).response("bar");
    (API)

    {
      "request" :
        {
          "text" : "foo"
        },
      "response" :
        {
          "text" : "bar"
        }
    }
    (JSON)

    如果请求内容过大,我们还可以把它放到文件里:

    server.request(by(file("foo.request"))).response("bar");
    (API)

    {
      "request" :
        {
          "file" : "foo.request"
        },
      "response" :
        {
          "text" : "bar"
        }
    }
    (JSON)

    • URI

    有时,我们主要关注URI,那我们可以这样配置:

    server.request(by(uri("/foo"))).response("bar");
    (API)

    {
      "request" :
        {
          "uri" : "/foo"
        },
      "response" :
        {
          "text" : "bar"
        }
    }
    (JSON)

    URI常常与某些参数相伴,可以针对不同参数配置不同的返回:

    server.request(and(by(uri("/foo")), eq(query("param"), "blah"))).response("bar")
    (API)

    {
      "request" :
        {
          "uri" : "/foo"
          "queries" : {
            "param" : "blah"
          }
        },
      "response" :
        {
          "text" : "bar"
        }
    }
    (JSON)

    • HTTP

    REST让人们重新认识了HTTP动词的价值,Moco当然也不会错过:

    server.get(by(uri("/foo"))).response("bar");
    (API)

    {
      "request" :
        {
          "method" : "get",
          "uri" : "/foo"
        },
      "response" :
        {
          "text" : "bar"
        }
    }
    (JSON)

    这里是get,当然post也是很常见的:

    server.post(by("foo")).response("bar");
    (API)

    {
      "request" :
        {
          "method" : "post",
          "text" : "foo"
        },
      "response" :
        {
          "text" : "bar"
        }
    }
    (JSON)

    除了HTTP动词,另一个越来越受关注的是HTTP头:

    server.request(eq(header("foo"), "bar")).response("blah")
    (API)

    {
      "request" :
        {
          "method" : "post",
          "headers" : {
            "content-type" : "application/json"
          }
        },
      "response" :
        {
          "text" : "bar"
        }
    }
    (JSON)

    • XPath

    Web Service的火爆让XML大行其道,根据XML内容进行判断的一种方式就是XPath:

    server.request(eq(xpath("/request/parameters/id/text()"), "1")).response("bar");
    (API)

    {
      "request" :
        {
          "method" : "post",
          "xpaths" : {
            "/request/parameters/id/text()" : "1"
          }
        },
      "response" :
        {
          "text" : "bar"
        }
    }
    (JSON)

    应答(Response)

    • 内容

    最先想到的一定是返回特定的内容,其实之前已经看到了:

    server.request(by("foo")).response("bar");
    (API)

    {
      "request" :
        {
          "text" : "foo"
        },
      "response" :
        {
          "text" : "bar"
        }
    }
    (JSON)

    同请求一样,如果内容很多,就放到文件里:

    server.request(by("foo")).response(file("bar.response"));
    (API)

    {
      "request" :
        {
          "text" : "foo"
        },
      "response" :
        {
          "file" : "bar.response"
        }
    }
    (JSON)

     

     

    • HTTP

     

    Moco支持应答中的HTTP状态码:

    server.request(by("foo")).response(status(200));
    (API)

    {
      "request" :
        {
          "text" : "foo"
        },
      "response" :
        {
          "status" : 200
        }
    }
    (JSON)

    我们还可以指定HTTP应答Header里的内容:

    server.request(by("foo")).response(header("content-type", "application/json"));
    (API)

    {
      "request" :
        {
          "text" : "foo"
        },
      "response" :
        {
          "headers" : {
            "content-type" : "application/json"
          }
        }
    }
    (JSON)

     

    • URL

    有时,我们也可以请求转发到另外一个网站上,换句话说,Moco这时扮演了一个代理的角色:

    server.request(by("foo")).response(url("http://www.github.com"));
    (API)

    {
      "request" :
        {
          "text" : "foo"
        },
      "response" :
        {
          "url" : "http://www.github.com"
        }
    }
    (JSON)

    • 顺序应答

    有时,我们希望模拟一个改变服务器端资源的真实操作,比如:

    1. 当我们发起第一个get请求时,服务器返回“foo”。
    2. 然后,我们发起了一个post请求,改变了服务器端资源。
    3. 当我们再发起请求时,我们希望服务器返回“bar”。

    Moco支持我们对同一个请求返回不同的值:

    server.request(by(uri("/foo"))).response(seq("foo", "bar", "blah"));
    (API)

    如你所见,第一次请求会返回foo,第二次会返回“bar”,第三次则是“blah”。

    以上就对Moco的基本功能做了一个快速浏览。嗯,就是这么简单!

    如果你在项目里经常遇到各种各样的集成需求,尤其是HTTP方式的集成,不妨试试Moco,希望它可以让你的开发更简单一些。当然,作为一个年轻的项目,Moco欢迎各种各样的想法。

  • Moco是一个用以简化测试服务器搭建的框架,主要做测试和集成之用。

    起因

    所谓企业级开发,多半都意味着有一大堆系统要集成,时至今日,最为流行的集成方式莫过于通过Http协议,无论是Web Service,抑或是REST架构。在我的开发记忆里,有人会安装一个web server,然后放进去一些静态文件,稍微复杂的点,自己写一个Java应用,部署起来,做所谓的动态响应,更有甚者,我要搭建一个Web容器,比如Tomcat。总而言之,麻烦。

    简单是一个好的开发人员永远应该追求的,再经历了无数次集成的痛苦之后,Moco向简化这种繁琐集成迈出了一步。闲话少叙,上例子。

    用法

    Moco目前有两种使用方式,一种是API,一种是独立运行。

    下面是一个API的例子,其实这就是一个普通的JUnit测试:

    @Test
    public void should_response_as_expected() {
      MocoHttpServer server = httpserver(8080);
      server.reponse("foo");

      running(server, new Runnable() {
        @Override
        public void run() {
          try {
            Content content = Request.Get("http://localhost:8080").execute().returnContent();
            assertThat(content.asString(), is("foo"));
          } catch (IOException e) {
            throw new RuntimeException(e);
          }
        }
      }
    }

    这里我们搭建了一个http服务器,端口是8080。我们期望访问的时候,它能够返回foo。然后,调用running方法,我们就有了一个环境,启动了一个真正的服务器。这个例子里,我们用了Apache的Http Client Fluent API去访问这个本地启动的服务器。如你所见,当我们访问时,它会给我们返回字符串“foo”。也许你已经想到了,如果我们设置的这个字符串如果是一个SOAP形式,它就模拟了一个Web Service。

    嗯,就是这么简单!

    有时候,我们不仅仅是想在测试里用它,而是希望搭建一个独立的测试服务器。这就是Moco另一种形式发挥作用的时候,只要我们给它提供一个配置文件,声明我们所需服务的样子。目前Moco支持配置文件的格式是JSON。下面是一个例子:

    {
      "port" : 8080,
      "sessions" :
        [
          {
            "response" :
              {
                "text" : "foo"
              }
          }
        ]
    }
    (foo.json)

    这个例子同之前一样,当我们访问服务器时,我们期待返回的是一个字符串“foo”。这个配置同之前的API如出一辙,就不多做解释了。我们把它运行起来:

      java -jar moco-runner--standalone.jar foo.json

    打开你的浏览器,输入http://localhost:8080/,“foo”就呈现在你面前了。

    嗯,就是这么简单!

    当然,Moco能做的远不止这些,接下来,我们会飞快地浏览Moco的功能,请系好安全带,我们开始了。