u-ryo's blog

various information for coding...

Category: Spring

Mock Web Server by WireMock on JHipster

| Comments

外部web siteのresponseのtestにWireMockを使ったのでメモ。

参考: WireMockを使って通信に関するテストをやってみよう

使う時はbuild.gradleで、

1
testCompile 'com.github.tomakehurst:wiremock-standalone:2.15.0'

して(wiremock-standaloneでないと、 jetty系のClassNotFoundExceptionに見舞われた)から、 test classの方で、

1
2
3
4
5
import com.github.tomakehurst.wiremock.junit.WireMockRule;
import static com.github.tomakehurst.wiremock.client.WireMock.*;
    :
    @Rule
    public WireMockRule wireMockRule = new WireMockRule(8089);

とし、各test method内で、

1
2
3
4
5
6
7
        stubFor(post(urlPathEqualTo("/any_path"))
                .withRequestBody(matching(".*arr_stn=%96%BC%8C%C3%89%AE.*"))
                .withRequestBody(matching(".*dep_stn=%93%8C%8B%9E.*"))
                .withRequestBody(matching(".*train=1.*"))
                .willReturn(aResponse()
                            .withStatus(200)
                            .withBodyFile("for_test.html")));

とstatic importした com.github.tomakehurst.wiremock.client.WireMock.stubForを使えば良い。 matching rule詳細は公式ページを。 post body requestに対して.withQueryParam("...", matching("..."))は効かなかった。 また、matchingなので全体にmatchingさせる必要があることに注意。 find系はなし。

test対象classでのqueryがここでの指定に合わなければ、404がthrowされる。

上記のように、test用に既に用意してあるhtml fileを.withBodyFile("...")で指定でき、 実体はsrc/test/resources/__files/に置く。 .withBodyFile(...)は公式Docに無く、JavaDoc APIも無いので、 Github上のsource codeを眺めて見つけた。

こうしたmappingをsrc/test/resources/mappings/以下にanyName.jsonを置いて、 requestとresponseを指定できるのだが、 それだと「こういうrequest bodyの時はこのようなresponse、 こっちの時はこれこれのresponse」というように、 複数の条件を書くことが出来なかった (書いてみると、最後のrequest/response条件しか効かず。 ならばと複数fileに分けてみたら、 file名alphabeticalで最後のrequest/responseしか効かず)。 なので、Javaでstub条件書いて各test method内に書くしか無い?

これはSpringの話だが、 testの時だけlocalhost:8089を見るようにするには、 〜.config.ApplicationProperties classにfield定義、setter/getterのaccessorを付け、src/main/resources/config/application.ymlapplication:以下に通常時の値を、src/test/resources/config/application.ymlapplication:以下にtest時の値(この場合はhttp://localhost:8089/any_path)を記述。 実際に使うには、

1
2
    @Autowired
    private ApplicationProperties properties;

とDIさせて、properties.get〜()と普通にgetterでget。

AssertJ、method chainで書けるので、 static importがassertThatだけで済んでいいですね。

これはJHipsterの話?(Springかなぁ?)で、 Test Class内で@Autowiredしてるservice classのmockが必要になったら、 特にbuild.gradeに記述せずともimport org.mockito.Mock;して mockitoの@Mockが使えるんですね。 そう言えば、元々import org.mockito.MockitoAnnotations;されてますね。

Unit Test on Jhipster

| Comments

JHipsterでのunit testでいくつかハマったのでメモ。

  • unit test実行はgradle test

  • 単一test classを指定するにはgradle test --tests FQDN.class.name

  • test class内のlog.debug(...)を出力するには、build.gradletest.testLogging.showStandardStreams = trueを記述し、またsrc/test/resources/application.ymlに以下を記述(src/main/resources/application-dev.ymlには記述あり)

    1
    2
    3
    4
    5
    
    logging:
        level:
            ROOT: DEBUG
            bz.mydns.walt.canmatch: DEBUG
            io.github.jhipster: DEBUG
    

  • test classにおいてRESTで認証したuserを表したい時には@WithMockUser(import org.springframework.security.test.context.support.WithMockUser;)

  • testされる側のREST methodでは、java.security.Principalを勝手に引数に加えることでlogin userを取得出来、それをtestする場合にはtest classのMockMvcrestUserInfoMockMvc.perform(post("/api/user-infos")...)などとする時にorg.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.userを使って.user("foo")とすると、認証された"foo"というuserでのaccessをmock出来るが、org.springframework.security.core.context.SecurityContextHolder.getContext().getAuthentication()というstatic methodでjava.security.Principalが取れ、それに.getName()で名前を取得可能。その方がspringっぽいのかな。test class側では、認証accessが必要なmethodに上記の@WithMockUserをつければ良い。@WithMockUser(username="foo", password="pw", roles={"USER"})などとすれば色々指定可能だが基本的には不要

参照

Web Push Notification for PWA

| Comments

PWAの売りとしてWeb Push Notificationが出来るのはつとに聞いていましたが、 具体的にどうやるんだろう? と思ってました。 仕組みから考えて、WebSocketが必須なのかな? とかって勝手に思ってたんですが、違うんですね! 「やってみた」系の記事見て、Firebaseを使うものばっかりだったので、 えーっ! Firebase hosting必須なの?! と思いきや、 VAPID(Voluntary Application Server Identification)も あるということで一安心。 あーでも、別にFCM(Firebase Cloud Messaging)をhostingじゃなく使うことも出来るので、 その方が楽なのかな。 browser毎じゃなくてuser毎に送れるっていうし。 でもvendor lock onも嫌なので、 VAPIDからやってみました。

Web Pushの仕組みは、PWAのプッシュ通知の仕組みがよくまとまってました。 PWAとWeb Push Notificationは別なんですね。

browser上からは、Push Demoなどですぐ試せます。

自分の手に馴染ませないとよくわからないので、作ってみました

  1. push server側で、VAPIDのkey(public, private)を決めておく。key generationは、web-pushclient.jsをcommand lineから使って$ node_modules/web-push/src/cli.js generate-vapid-keysとせよ、とか書いてあるものも他に多くありましたが、GoogleのAdding Push Notifications to a Web AppにあるようにPush Companionで簡単に作れますね。試しに作ってみたものではscriptで毎回自動生成してclient(=web browser, html and javascript側)に読ませてみるようにしてみましたが、実運用上はベタに固定値書いておいた方が楽だしわかりやすいし再起動の度に値変わらないからその方がいいのかな。
  2. あとserver側は各clientのnotificationEndPoint、publicKey、authを知ってないといけない。これらはclientがserviceWorkerRegistration.pushManager.subscribe(subscribeParams)した時に得られる。subscribeParameterとは、server側のpublicKeyをurlB64ToUint8Array()したものとuserVisibleOnly: trueは固定値。そうして得られた3つの値をserver側に引き渡さねばならないのだが、そのまま渡すのではなく、btoa(String.fromCharCode.apply(null, new Uint8Array(key)))などとしてString化する必要がある。
  3. 上記でもらうnotificationEndPointって、mozillaならhttps://updates.push.services.mozilla.com/wpush/v2/...、chrome(chromium)ならhttps://fcm.googleapis.com/fcm/...というように、browser毎にmaker別のpush server持ってるんですね。ってことは、browserは裏ではこうしたsiteと常にconnection張ってるということ?
  4. もっと深い原理(具体的にどういうheader/encryptionでpush serviceに送っているか)はWeb Pushの実装まとめ(Chrome/Firefox/Android共通)参照。
  5. client側では、main.jsで、まずNotification.requestPermission()で許可を得る。許可を求めるtimingは、page load直後かと思いきや、最初にいきなり許可求めると拒否られることが多いということで、button式に。button押したら許可求める、と。
  6. 許可が得られたらnavigator.serviceWorker.register('/sw.js')でService Worker scriptを登録。navigator.serviceWorker.readyで利用可能になるのを待つ。これのreturn value(の型)はServiceWorkerRegistrationでありnavigator.serviceWorkerではないので注意。これからpushManagerを得ることになる。
  7. その後、push serverからserverのpublicKeyをGET。
  8. subscribeParamsを作ってserviceWorkerRegistration.pushManager.getSubscription(subscribeParams)してみる。既に登録があったらunsubscribeしないとならない。上述のように、parameterではserver側のpublicKeyをurlB64ToUint8Array()したものとuserVisibleOnly: trueは固定値。
  9. それからserviceWorkerRegistration.pushManager.subscribe(subscribeParams)でsubscribeする。
  10. そうすると、endpoint、key、authが得られるので、それらをpush server側に通知。
  11. sw.jsでは、push通知が喜た場合push eventがfireされるので、event.waitUntil(registration.showNotification(...)する。このregistrationがどこから来るのかよくわからなかったが、ともあれ明示的にwaitUntilしてshowNotificationしないと表示されない(pushが来て勝手に表示されるわけではない)。
  12. 表示されたNotificationをclickするとnotificationClick eventがfireされるので、clickしたらどこかへ遷移したい場合にはこのevent listenerをsw.jsに書いておく必要がある。
  13. PWA用に、sw.jsinstall eventとfetch event listenerでfile chacheの作成とその利用をcodeする(installでcacheに加え、fetchではcacheにあったらそれを、なければfetchするようにする)。
  14. notificationをpushするには、push server側でまずnew PushService(publicKey, privateKey, "http://localhost")してから、clientから貰った情報でpush.send(new Notification(...))する。中では色々と暗号化しているが、nl.martijndwarsのwebpush-javaを使えばVAPIDのkey生成やnotificationのsendもone methodでよろしくやってくれる。他の言語もweb-push-libsに各種あり。

実際に自分で真似して書いてみて、 値やcodeを色々変えて試してみることで、 大分わかってきました。

JHipster

| Comments

ここんとこPWA(Progressive Web Application)作ろうとしていて、 ずっとJEEかなぁって思ってたんですけどSpringの勢いが凄くて、 それと今ならSPAでぼくはAngular派なので、 それに今時認証は自分でやりたくないよねとOAuth2で、 連動させるの大変だなぁとかって思ってたら、 そういうの全部ひっくるめたJHipsterというのがあるんだ! というのが衝撃でした。 かなり前からあったんですね。

ただこれ難しいですね。 scaffoldingまでは簡単ですけど、 そこからどうしていいのか。 自動生成filesはなるべく弄りたくないですが、 そうもいかないですよねこれ。 samplesも、「そこから先」についてはなかなか。 JHipster Mini-Book 4.0も見てみましたものの、それをなぞって行けるわけでは無かったので。 scaffoldingと、JDLでentities作って、 jhipster spring-controller Foong g component Barとかのcommandで取っ掛かりを作って、 そこから作り込んでいく、感じでしょうか。

Angular CLIがうまく動かなかった等あったのでメモ。 jhipsterのversionが変われば、こんな知識も不要になると思われますが。

  • JDLでfield名はsnakeやhyphenationではなくcamelで。 DBだからhyphenかなと思ったんですが、 生成されたclassのfield名がcamelにならなかったので。
  • JDLでUserは特別なentity。 Manualに書いてありました。 scaffoldingの次は取り敢えず JDL Studioでentity作成、import、 になるのに、解説すっ飛ばしてJDL見たので、なんだろう?って思うんですよね。
  • jhipster 4.13.3で入るAngular CLI(./node_modules/@angular/cli/)のversionは 1.6.0(package.json)でbug持ち(@angular-devkit/coreが入らない)なので、 ng g ...が失敗します。package.json中の@angular/cliの version numberを最新のもの(本執筆時点では1.6.6)に書き換え、 yarn installするとうまく行きました (Angular CLIをglobalでinstallしてもlocalのversionのモノを使うのでダメ、 @angular-devkit/coreを個別にnpm install ...しても、 次から次へと足りないものが出て来て、 全部入れた挙句の果てにyarn startがコケるようになるのでダメ)。
  • ng g component Fooとしてそれを表示させるにはどうしたらいいんだろう? と悩みました。route作ってそこで表示させるべく、他のentityとか見よう見真似でfoo.route.ts作ってpath直打ちして試したものの、表示されず。errorも何も出ないので、何が悪いのか分かりません。多分登録が届いてないんだろうとは思ってたんですけど、具体的にどこをどう直せばいいのかわけわかめです。StackOverflowに回答があったので出来ました。結局、0.ng g component Fooでcomponentを作る 1.foo.route.tsを作る(その際Routesで配列にするのではなくRouteで単数に?) 2.index.tsを真似して作る(foo.moduleのimportは記載不要) 3.foo.module.tsを作る 4.app.module.tsに登録(その際Angular CLIが自動登録したcomponentは削除しないとtop pageが表示されず) →そもそもcomponentではなくmoduleをgenerateするべきだったのでしょうか。よくわかりませんが、scaffoldingの後のことはなかなか書いてないので、「こんなのわかんないよー」という感じです。

JHipsterは「Java/Angular版Rails」か! とも思ったんですが、 もう出てだいぶ経つみたいですし、 でもまだそこまで洗練されてはいないのかなと。 Angularの難しさ、面倒臭さ、 とnode modules周りの変化が激しいのもあるためか、 使いこなすのはなかなか大変だと思います。