ふるてつのぶろぐ

福岡在住のエンジニアです。

写真提供:福岡市

今度は客先でコンテナ勉強会

今日すること

f:id:tetsufuru:20190806104529p:plain:w100
こんにちはふるてつです。

ただいま客先の勉強会にてDockerを勉強中で、まずは入門としてこちらのサイト「入門Docker」を勉強しております。
https://y-ohgi.com/introduction-docker/
今回は少なめですが、「コンポーネント」 メニューの中から「volume」ページをもくもくとしました。
またいつものように勉強した内容を書きます。

f:id:tetsufuru:20191002191552p:plain:w500

ボリュームはデータを永続化するための機能になります。

1. Volume

Volumeには2つの種類が存在するそうです。

1-1. Data Volume

Volume は Docker Container のライフサイクルの外で管理されるファイル/ディレクトリとのことです。
ではまず Volume の方を試してみます、下記のコマンドでボリュームをマウントしてみます。
docker run -v /tmp/text ubuntu touch /tmp/text/hogefugapiyo

ところがこちらを流しても、なにもおこりません🤢
"hogefugapiyo"というファイルができてエクスプローラーで見えるはずですが、どこにもファイルができません。
わたしの場合ホストOSがWindowsなので、どうやら基本的にC:ドライブを含んだ書き方にしないとならない模様です。
というわけでやむなく下記のように-v <ホストOSのディレクトリ>:<コンテナのディレクトリ>と書き替えました。
こちらはファイルができました。

docker run -v /c/tmp:/tmp ubuntu touch /tmp/text/hogefugapiyo

下図のようにエクスプローラーでファイルが見えました、よしよし ✨ f:id:tetsufuru:20191002224956p:plain:w400

その昔、VirtualBoxでも同じようなことができてましたねぇ、少し思い出しました。

あとはvolume が作成されたか確認のために、docker volume lsを実行します。 うーむ、しかし volume は1件も表示されません。
volumeのところはなんだか難しいですね、Windows版のDockerだと表示されないのかも🤔
ここは気を取り直して次にいきます。

1-2. Data Volume Container

次は、Data Volume Container。
他のDocker Container で指定されているVolumeを参照するための機能とのことです。

1-1 の Data Volume は他のコンテナからは見えないんですね多分。
コンテナ同士で値を引き渡すときなどに使うのでしょう。

では下のコマンドを試します。
docker run --name volume-test -v /tmp/test ubuntu touch /tmp/test/{hoge,fuga,piyo}

今度はエラーになりました。
Dockerubuntuだからか、複数ファイルのtouchコマンドを受け付けていないようです。

docker run --name volume-test -v /tmp/test ubuntu touch /tmp/test/{hoge,fuga,piyo}
発生場所 行:1 文字:72
+ ... --name volume-test -v /tmp/test ubuntu touch /tmp/test/{hoge,fuga,piy ...
+                                                                 ~
パラメーター一覧に引数が存在しません。
    + CategoryInfo          : ParserError: (:) [], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : MissingArgument

仕方がないので、こちらも1ファイルだけに書き直します。
C:ドライブのところも同じく書き直します。

 docker run --name volume-test -v /c/tmp:/tmp ubuntu touch /tmp/tesst/piyo

今度はエラーになりません。

では、別のコンテナを新しく起動して、さきほど作成した volume-test コンテナのファイルへアクセスできるか確認してみます。
docker run ubuntu ls -l /tmp/test

なるほど確かにファイルを認識できていません。

docker run ubuntu ls -l /tmp/test
ls: cannot access '/tmp/test': No such file or directory

次はData Volume Containerの方のコマンドを実行します。
docker run --volumes-from volume-test ubuntu ls -l /tmp/test

docker run --volumes-from volume-test ubuntu ls -l /tmp/test
total 0
-rwxr-xr-x 1 root root 0 Oct  2 11:29 piyo

こちらではファイルを認識しました。
volume には自コンテナ内のみと他コンテナから見れるものと2種類あるんですね。

今日の感想


今回もまた「入門Docker」の内容をそのまま、まとめた内容になりました。
volumeのところは少し難しく途中で詰まることがあり、その分内容も少し短くなりました。
あっさりした内容で申し訳ありませんが、次は頑張ります✨

それではまた

今夜は社内AWSもくもく会2 - Elastic Beanstalkをもういちど

今日すること

こんにちはふるてつです。

今回もElastic Beanstalkによる構築レスなサイト作りをおこないます。
前回は思ったように扱えず、サブネットやセキュリティーグループが新たに数個増えよくわからない状態になりました。
そこで今回は事前にサブネットやセキュリティーグループを準備しておくことにしました。RDSも今回は作っておきます。

前回の内容と重複するところがありますが最初から書いていきます。

Elastic Beanstalkの作成

AWSにログインし、サービス一覧からElastic Beanstalkをクリックします。 f:id:tetsufuru:20190908091336p:plain

f:id:tetsufuru:20190908091459p:plain 上の画面にて右上の方にある「新しいアプリケーションの作成」をクリックします。

f:id:tetsufuru:20190908093120p:plain 上記の画面が表示されますのでアプリケーション名を入力して「作成」ボタンをクリックします。

f:id:tetsufuru:20190908093413p:plain 環境の設定画面がでてきます。「今すぐ作成しましょう。」のリンクをクリックします。

f:id:tetsufuru:20190908093632p:plain 上記は"ウェブサーバー環境"を選択します。
下記の画面が表示されます。

f:id:tetsufuru:20190919063513p:plain f:id:tetsufuru:20190919063611p:plain

上記ウェブサーバー環境の作成は以下のように登録します。

  1. 環境名:EbWordPress-env
  2. ドメイン:ebwordpress-env
  3. プラットフォーム:事前設定済みプラットフォームでPHP
  4. アプリケーションコード:コードのアップロード
    (事前にダウンロードしておいたZIPファイルをアップロードします) ここで「より多くのオプションの設定」をクリックします。

オプションの設定

オプションの設定は下記の順番で設定したほうが良いです。
最初にインスタンスを設定しようとするとセキュリティグループを既存のものから選択できませんでしたので。

  1. ネットワーク
  2. セキュリティ
  3. インスタンス

ネットワークの設定

WordPressに作った既存のVPCを選択します。
インスタンスサブネットは2つのAZのそれぞれPublicの方のサブネットを選びます。
f:id:tetsufuru:20190919064545p:plain f:id:tetsufuru:20190919064810p:plain

セキュリティの設定

セキュリティの設定ですがサービスロールのところが3種類ほど選べます。
このロールがどう違うのかはよく分かりませんが、いったん「aws-elasticbeanstalk-service-role」にしました。
f:id:tetsufuru:20190919070243p:plain f:id:tetsufuru:20190919070533p:plain

インスタンス の設定

WordPress用のセキュリティグループを選択します。
この設定をネットワークより前のタイミングでおこなうとインスタンスセキュリティグループのところに「default」しか出てきません。
f:id:tetsufuru:20190919070934p:plain f:id:tetsufuru:20190919071023p:plain 今回は既存のものが出てきましたのでそちらを選びます(「WP-Web-DMZ」です)

そして最後に環境の作成画面に戻ってきたら「環境の作成」ボタンをクリックします。
今回は一発で無事作成できました。
f:id:tetsufuru:20190919071513p:plain

Elastic Beanstalkも正常に動いています、WordPressも動くようになりました。
f:id:tetsufuru:20190919182302p:plain

結果

今回の結果としてはサブネットが増えたりはしなくなりました。
しかし「ebWordpress-env」というセキュリティグループが増えてました。
EC2インスタンスの設定のところで指定したセキュリティグループの内容が複製されて新たにできているようです。

f:id:tetsufuru:20190919181431p:plain

すべてのアプリケーションから「EB-WordPress」の詳細を確認します。
緑色の付箋?みたいなところをクリックします。
f:id:tetsufuru:20190919180948p:plain

下記画面のインスタンスセキュリティグループを見ると、2種類のセキュリティグループが指定されています。
これはおそらくそういう仕様なんでしょうね。
であれば、はじめに指定した「WP-Web-DMZ」はもう不要なので削除したほうが良さそうです。
f:id:tetsufuru:20190919182937p:plain f:id:tetsufuru:20190919183119p:plain

感想


前回よりわたしElastic Beanstalkの操作がうまくなりました✨

余談ですが最後に確認した「EB-WordPress」の詳細画面には他のメニューで「モニタリング」や「ログ」があり、他に色々と使える機能がありそうでした。
しかし私の書籍ではElastic Beanstalkはいったん終わりで次のテーマに移ります。
もう少し勉強したかったのですが、それは別途またの機会に。

それではまた😎

モダンコーディング入門 - HTML5とCSS3 - 最後

今日すること

こんにちは、ふるてつです。
🍉盆休みを使って下の本でHTML5CSS3を勉強中でしたが盆中に終わらず、いまも勉強を継続しています、 今回もその中で新しく知ったことを書きます。
f:id:tetsufuru:20190815030218p:plain:w100

書籍中では3種類のレイアウトのサイトを作るのですが最後3つ目のレイアウトになりました。
今回は個人のポートフォリオサイトや商用のランディングページなどで多く見られる、1枚構成のシングルページサイトを作ります。

作ったサイトですが、下のように「タイトル」、「自己紹介」、「作品紹介」、「スキル紹介」、「問い合わせフォーム」と縦にならんでいきます。

f:id:tetsufuru:20190916000741p:plain

f:id:tetsufuru:20190916000807p:plain

f:id:tetsufuru:20190916000833p:plain

f:id:tetsufuru:20190916000853p:plain

実際に出来上がったサイトはこちらです、せっかくですのでGithub pagesで見れるようにしています(ボタンはクリックしても動きません)
https://tetsujifurukawa.github.io/learning_modernCodingOfHtml5Css3/singlepage-layout/index.html

● display:table

書籍も終盤になりあまりこれ知らなかったということはなくなってきました。
display プロパティの所のみはじめてだったので書きました。
display プロパティには block、inline、inline-block の他に、table、table-cell という値を設定でき、 display:table を使用すると従来のテーブルレイアウトのようなことをCSSだけで実現できます。
これはわたしはじめて知りました。
Bootstrapを使ったりCSS Grid Layoutのような手法でないと、似たようなことはできないと思ってました、 知らないというのは怖いですねぇ🤢
書き方は下記で、親要素のCSSdisplay: table;を設定して、子要素にdisplay: table-cell;を設定します。
HTMLの例

<div class="parent">
  <div class="child">hoge1</div>
  <div class="child">hoge2</div>
  <div class="child">hoge3</div>
</div>

CSSの例

.parent{
  display: table;
  width: 100%;
}
.child{
  display: table-cell;
}

スマートフォン対応(レスポンシブ)

スマートフォン対応はメディアクエリを使用します。
メディアクエリは使ったことがありますので、全然知らないようなことはなかったです。
参考までにどんな作業をしたか箇条書きにしました。

  1. display:tableで横並びにした写真は、display:tableを解除して縦並びにする。
  2. 文字サイズは少し小さめにする。
  3. 上下の余白を少し狭くする。
  4. button:hoverは無効にする。

携帯サイズだと下記のようになります。
f:id:tetsufuru:20190917004919p:plain:w300

f:id:tetsufuru:20190917005131p:plain:w300

f:id:tetsufuru:20190917005210p:plain:w300

f:id:tetsufuru:20190917005240p:plain:w300

f:id:tetsufuru:20190917005309p:plain:w300

今日の感想

盆休みに始めた書籍の勉強ですが最後の3種類目が9月にやっと終わりました。
なかなかに勉強しがいがありました、新しく知ったことも沢山ありました。
しっかり忘れないようにして、役立てていこうと思います。

では、今日もお疲れ様でした。

Angular8 で Web アプリを作ろう - Angular もくもく会 in Fukuoka

こんにちはふるてつです。

昨日は ng_fukuoka さんが開催する「Angular もくもく会 in Fukuoka #9」に参加しました。
19時からもくもく開始で、20時からLTが2つほどありました。
ng-fukuoka.connpass.com

もくもく会の様子

会場は株式会社ベガコーポレーションさんで、このような感じでした。
常連さんの他に、Angularをこれから始められる方が新たに2~3名来られてました。
f:id:tetsufuru:20190912180745p:plain

LTの内容

●まずはわたくしのLTの番。
Angularもくもく会でのLTは2回目ですが、話下手なのでやはり緊張します。
テーマは主にJasmineでの単体テストについてです。
これまでブログで書いたJasmineについての内容をまとめた感じで話しました。
speakerdeck.com

●次は ng_fukuoka 代表の新福さんのLT。
NgRxを使った状態管理’についてです。
使ってみたいですね、すごく面白そうでした。
もっと勉強せねば、と思いました。
speakerdeck.com

今日の感想


無事LTを終えてホッとしました。
だいぶ緊張もしましたが、質問も少しいただけたし、苦手ながらやはり話をして良かったと思いました。
見直したほうが良い所も見つかりましたし、ありがたく思います。

次回もまたLTできるようレベルアップせねばと思う昨日でした✨

では。

今度は客先でコンテナ勉強会

今日すること

f:id:tetsufuru:20190806104529p:plain:w100
こんにちはふるてつです。

ただいま客先の勉強会にてDockerを勉強中で、まずは入門としてこちらのサイト「入門Docker」を勉強しております。
https://y-ohgi.com/introduction-docker/
今週は「コンポーネント」 メニューの中から「container」、「network」ページをもくもくとしました、では勉強した内容を書いていきます。

1. Container

f:id:tetsufuru:20190908143541p:plain:w500

言わずと知れたDocker Containerです。
Docker ImageがスナップショットだとするとDocker Containerはその「スナップショットから起動したプロセス」だとのこと。
意識する点はDocker Containerは1つのコマンドをフォアグラウンドで動かすように設計されていることだそうです。

1-1. ライフサイクル

f:id:tetsufuru:20190908145126p:plain

Docker Containerの状態は5つあるとのことです。

  1. Image
  2. RUNNING
  3. STOPPED
  4. PAUSED
  5. DELETED

4番目のPAUSEDはユーザーがコマンドを発行しないとならない模様なので、あまり覚える必要はないかもしれません。

2. Network

f:id:tetsufuru:20190908150106p:plain:w400

Dockerはネットワークの振る舞いを定義することが可能で、デフォルトでは2種類のNetwork Driver が存在するそうです。

  1. bridge
  2. host

ネットワークのところは少し難しそうですね。
昔、VirtualBoxの時にもブリッジとかホストとか出てきて困惑しましたけど、同じものですかねー、いやだなあ🤢
とりあえず1番目のbridgeというのだけを覚えようかとは思います。

2-1. ネットワークを試す

● デフォルトで存在するネットワークの確認

docker network lsコマンドでDockerが管理しているNetwork一覧を出力します。

docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
578fc5351093        bridge              bridge              local
4b353041c00c        host                host                local
f5018c1f7037        none                null                local

3件表示されました。bridgehost以外にnoneというのもあります。
これらはデフォルトで存在するものと思います。

次にホスト側のネットワークも確認します。
勉強しているサイトではip aコマンドですが、わたしはWindows環境なのでipconfig -allで確認します。
DockerNATというネットワークがありました、多分これですね。

Windows IP Configuration
~ 中略 ~
Ethernet adapter vEthernet (DockerNAT):

   Connection-specific DNS Suffix  . :
   Description . . . . . . . . . . . : Hyper-V Virtual Ethernet Adapter #2
   Physical Address. . . . . . . . . : xx-xx-xx-xx-xx-xx
   DHCP Enabled. . . . . . . . . . . : No
   Autoconfiguration Enabled . . . . : Yes
   IPv4 Address. . . . . . . . . . . : xx.xx.xx.xx
   Subnet Mask . . . . . . . . . . . : xxx.xxx.xxx.xxx
   Default Gateway . . . . . . . . . :
   DNS Servers . . . . . . . . . . . : fec0:0:0:ffff::1%1
                                       fec0:0:0:ffff::2%1
                                       fec0:0:0:ffff::3%1
   NetBIOS over Tcpip. . . . . . . . : Enabled

● 新しいネットワークの作成

次は新しいbridgeネットワークを作成してみます。
docker network create myappコマンドを実行します。

docker network create myapp
ffb3874673689da21d1e10002140f92d5f40c2b1fd1fe085821eb7429a9b44e5

networkに myapp が増えていることを確認します。
docker network lsコマンド

docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
578fc5351093        bridge              bridge              local
4b353041c00c        host                host                local
ffb387467368        myapp               bridge              local
f5018c1f7037        none                null                local

myappが増えました。
ホスト側のネットワークにも追加されたことを確認しようとしましたが、違いは見れませんでした。 LinuxWindows環境の違いだと思います。

● Networkへnginxを参加させる

通信を受けるためのサーバーとしてnginxを myapp ネットワークに構築します。

docker run --name nginx --network=myapp -d nginx

● AmazonLinux2を起動し、nginxコンテナへ接続する

bridgeネットワークの場合、同一ネットワークのコンテナにはコンテナ名で名前解決が可能だそうです。
nginxと疎通できるか myapp ネットワーク内にAmazonLinux2イメージでコンテナを起動し、 curl を実行してみます。
docker run --network=myapp -it amazonlinux:2 curl nginx:80

docker run --network=myapp -it amazonlinux:2 curl nginx:80
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

なるほど確かに疎通できました。

● 新しくネットワークを作成し、疎通できないことを確認する

では myapp2 というネットワークを作成し、nginx2 という命名nginxを起動します。

docker network create myapp2
docker run --name nginx2 --network=myapp2 -d nginx

myapp ネットワークに所属しているAmazonLinux2から curl を実行し、疎通できないことを確認します。

$ docker run --network=myapp -it amazonlinux:2 curl nginx2:80
curl: (6) Could not resolve host: nginx2

なるほどたしかに疎通できませんでした。

今日の感想


今回もあいかわらず「入門Docker」の内容をそのまままとめた内容になりました。
コンテナのページは少ししかなかったので、 主にネットワークについて勉強しましたが、bridgeを使用すれば大体がつながりそうですね。
今回勉強してネットワークはそれほど恐れることはないかもと思いました。

それではまた

今夜は社内AWSもくもく会2 - Elastic Beanstalk

今日すること

こんにちはふるてつです。

夏も終わりですねぇ🍉、もう9月に入りました。
今回はElastic Beanstalkのお話です。
Elastic Beanstalkによる構築レスなサイト作りをおこないます。
少し前にEC2を使用してWordPressサイトを構築しましたが、同じことを、Elastic Beanstalkで行います。
しかしあまりうまく使えなかったので軽い失敗😢の記事になります。

WordPressのZIP入手

まずWordPressのZIP版を入手します。
AWSで作業する前に下記のサイトから入手しておきます。
f:id:tetsufuru:20190908091038p:plain https://ja.wordpress.org

Elastic Beanstalkの作成

AWSにログインし、サービス一覧からElastic Beanstalkをクリックします。 f:id:tetsufuru:20190908091336p:plain

f:id:tetsufuru:20190908091459p:plain 上の画面にて右上の方にある「新しいアプリケーションの作成」をクリックします。

f:id:tetsufuru:20190908093120p:plain 上記の画面が表示されますのでアプリケーション名を入力して「作成」ボタンをクリックします。

f:id:tetsufuru:20190908093413p:plain 環境の設定らしき画面がでてきました。「今すぐ作成しましょう。」のリンクをクリックします。

f:id:tetsufuru:20190908093632p:plain 上記は"ウェブサーバー環境"を選択します。
下記の画面が表示されます。

f:id:tetsufuru:20190908094505p:plain f:id:tetsufuru:20190908094208p:plain

上記ウェブサーバー環境の作成は以下のように登録します。

  1. 環境名:EbWordPress-env
  2. ドメイン:hogehoge_ebwordpress-env
    ドメイン名は一意なものにしないとおこられます)
  3. プラットフォーム:事前設定済みプラットフォームでPHP
  4. アプリケーションコード:コードのアップロード
    (事前にダウンロードしておいたZIPファイルをアップロードします)

書籍ではここで同時にネットワークの設定や、RDSの作成もおこなうように書いてあったのですが、 書籍に掲載されている画面と実際の画面が若干違い、良くわからなかったため「環境の作成」ボタンのほうをクリックしました。
書籍通りにするには「より多くのオプションの設定」をクリックすべきでした🤢
f:id:tetsufuru:20190908101014p:plain f:id:tetsufuru:20190908101129p:plain しかし一応アプリケーションが一つできました、まだ間もないのでステータスは"Pending"になっていますが数分したらグリーンになりました。

f:id:tetsufuru:20190908101659p:plain インスタンスもできました。
しかしRDSをそもそも作っていないので、WordPressは動きません。
というわけでもう一度最初からやりなおして、今度は「より多くのオプションの設定」を使用するようにします。

数回ほどやり直し

今度はアプリケーション名は"EbWordPress-env2"にします。
最初からやり直して、Webサーバー環境の作成で「より多くのオプションの設定」ボタンをクリックします。
f:id:tetsufuru:20190908103011p:plain f:id:tetsufuru:20190908103338p:plain 上記の画面が表示されます。

  1. インスタンス
  2. セキュリティ
  3. ネットワーク
  4. データベース

上記のうち4項目を設定しました、そして「環境の作成」ボタンをクリックします。

f:id:tetsufuru:20190908103950p:plain すると今度はエラーが出ました、セキュリティグループを作ることができない?だとか。
しかたないのでネットワークあたりの設定を見直して、もう一度"EbWordPress-env3"を作ります。
結局3でも、4でもエラーになり、面倒くさくなってアプリケーション名を"aaaa"にした頃にうまく動きました。
どこが結局悪かったかは申し訳ありませんがよく分かりません。

f:id:tetsufuru:20190908104605p:plain f:id:tetsufuru:20190908105608p:plain WordPressサイトも動きました。

できた環境について

RDSが自動で作られましたが、識別子がランダムな文字列なのがあまりうれしくないですね。
f:id:tetsufuru:20190908110014p:plain

f:id:tetsufuru:20190908110419p:plain RDSの情報とセキュリティを見ると、サブネットが3つ設定されています、なぜでしょうね。
あとVPCセキュリティグループは自動で作られたもの?か、作った覚えのないものが設定されています。

f:id:tetsufuru:20190908110920p:plain 上記の画面ですが、VPCメニューからサブネットの一覧をみると確かに3つ増えています。

f:id:tetsufuru:20190908111248p:plain セキュリティグループも増えました。
(できれば事前に自前で作ったものを使用したいです、どこかで操作を間違えたかも)

感想


今回はElastic Beanstalkのお話になりました。
しかしわたし的にうまくいかないことが多く、最後に出来上がった環境は少しぐちゃぐちゃになりました。
環境を一度に作れるのは便利ですが、RDSなどは事前に作っておいたほうが良い気もします。
次回もう一度チャレンジしたいと思います。

それではまた

Angular8 で Web アプリを作ろう - Jasmine - Componentのテスト その2 DOM

今日すること

こんにちは、ふるてつです。

前回、前々回とJasmineユニットテストについて書いていきました。
今回はComponentの DOM 部分のテストになります。

テストをするComponentの概要

今回テストをするのは前回と同様に会社マスターの一覧を検索する画面です。
すごく簡単です、検索条件は「会社名」、「会社名カナ」、「削除フラグ」の3です。
画面は下記になります(レイアウトはざっとしか作っていません今後きれいにします)
f:id:tetsufuru:20190831161753p:plain

DOM のテストを作るにあたり、下記の公式リファレンスを熟読しました。
https://angular.jp/guide/testing

コンポーネントの DOM のテスト」という章がありますので、 もしこれからテストコードを書かれる方はまず最初にこちらを参考にすると良いと思います。

テストをするComponentのHTMLとコード

前回はソースコードのみでしたが、今回はHTMLも掲載します。
それぞれ長いのでたたんでおきます。

テストをするComponentのHTMLはこちら(company-list.component.html)

<div class="mainColor">
  <div class="wrapper mainBody">
    <form [formGroup]="mainForm">
      <!-- Screen Title Area -->
      <div class="titleArea">
        {{ 'companyListScreen.title' | translate }}
      </div>

      <!-- Search Conditions Area -->
      <mat-card>
        <mat-card-content>
          <app-error-messages></app-error-messages>
          <div id="searchConditionsArea">
            <!-- -------------------- 1 -------------------- -->
            <div id="searchCondition1">
              <mat-form-field class="form-field">
                <input id="companyName" matInput type="text" formControlName="companyName"
                  placeholder="{{ 'companyListScreen.companyName' | translate }}">
              </mat-form-field>
            </div>
            <div id="searchCondition2">
              <mat-form-field class="form-field">
                <input id="companyKana" matInput type="text" formControlName="companyKana"
                  placeholder="{{ 'companyListScreen.companyKana' | translate }}">
              </mat-form-field>
            </div>
            <div id="searchCondition3">
              <label class="labelFor">{{ 'companyListScreen.deleted' | translate }}</label>
              <mat-checkbox id="deleted" formControlName="deleted"></mat-checkbox>
            </div>
          </div>
        </mat-card-content>
      </mat-card>

      <!-- Button Area -->
      <div id="searchButtonArea">
        <div id="paginatorArea">
          <mat-paginator [length]="resultsLength" [pageSize]="50" [pageSizeOptions]="[10, 50, 100]"></mat-paginator>
        </div>
        <div id="newBtnArea">
          <button mat-raised-button color="primary" id="newBtn" class="btn" (click)="onNew()"
            type="button">{{ "companyListScreen.newBtn" | translate }}
          </button>
        </div>
        <div id="clearBtnArea">
          <button mat-raised-button color="primary" id="clearBtn" class="btn" (click)="onClear()"
            type="button">{{ "companyListScreen.clearBtn" | translate }}
          </button>
        </div>
        <div id="searchBtnArea">
          <button mat-raised-button color="primary" id="searchBtn" class="btn" type="submit"
            (click)="onSearch()">{{ "companyListScreen.searchBtn" | translate }}
          </button>
        </div>
      </div>

      <!-- evaluationResult Area-->
      <div id="evaluationResult">
        <div class="loading-shade" *ngIf="isLoadingResults">
          <mat-spinner class="loading-spinner" *ngIf="isLoadingResults"></mat-spinner>
        </div>

        <div class="example-container">
          <table mat-table *ngIf="resultsLength>0" [dataSource]="searchCompanyDtos">
            <ng-container matColumnDef="companySeq">
              <th mat-header-cell *matHeaderCellDef style="width: 10%;">
                {{ "companyListScreen.searchResult.companySeq" | translate }}
              </th>
              <td mat-cell *matCellDef="let element"> {{element.companySeq}} </td>
            </ng-container>
            <ng-container matColumnDef="companyName">
              <th mat-header-cell *matHeaderCellDef>
                {{ "companyListScreen.companyName" | translate }}
              </th>
              <td mat-cell *matCellDef="let element"> {{element.companyName}} </td>
            </ng-container>
            <ng-container matColumnDef="companyKana">
              <th mat-header-cell *matHeaderCellDef>
                {{ "companyListScreen.companyKana" | translate }}</th>
              <td mat-cell *matCellDef="let element"> {{element.companyKana}} </td>
            </ng-container>
            <ng-container matColumnDef="companyAddress1">
              <th mat-header-cell *matHeaderCellDef>
                {{ "companyListScreen.searchResult.companyAddress1" | translate }}</th>
              <td mat-cell *matCellDef="let element"> {{element.companyAddress1}} </td>
            </ng-container>
            <ng-container matColumnDef="numOfEmployee">
              <th mat-header-cell *matHeaderCellDef>
                {{ "companyListScreen.searchResult.numOfEmployee" | translate }}</th>
              <td mat-cell *matCellDef="let element"> {{element.numOfEmployee}} </td>
            </ng-container>

            <ng-container matColumnDef="deleted">
              <th mat-header-cell *matHeaderCellDef>
                {{ "companyListScreen.deleted" | translate }}</th>
              <td mat-cell *matCellDef="let element"> {{element.deleted}} </td>
            </ng-container>
            <ng-container matColumnDef="evaluationSetting">
              <th mat-header-cell *matHeaderCellDef>
                {{ "companyListScreen.searchResult.evaluationSetting" | translate }}</th>
              <td mat-cell *matCellDef="let element"> {{element.evaluationSetting}} </td>
            </ng-container>
            <ng-container matColumnDef="createTime">
              <th mat-header-cell *matHeaderCellDef>
                {{ "companyListScreen.searchResult.createTime" | translate }}</th>
              <td mat-cell *matCellDef="let element"> {{element.createTime|date:'medium':timezone:locale}}
              </td>
            </ng-container>
            <ng-container matColumnDef="createUser">
              <th mat-header-cell *matHeaderCellDef>
                {{ "companyListScreen.searchResult.createUser" | translate }}</th>
              <td mat-cell *matCellDef="let element"> {{element.createUser}} </td>
            </ng-container>
            <ng-container matColumnDef="updateTime">
              <th mat-header-cell *matHeaderCellDef>
                {{ "companyListScreen.searchResult.updateTime" | translate }}</th>
              <td mat-cell *matCellDef="let element"> {{element.updateTime|date:'medium':timezone:locale}} </td>
            </ng-container>
            <ng-container matColumnDef="updateUser">
              <th mat-header-cell *matHeaderCellDef>
                {{ "companyListScreen.searchResult.updateUser" | translate }}</th>
              <td mat-cell *matCellDef="let element"> {{element.updateUser}} </td>
            </ng-container>

            <tr mat-header-row *matHeaderRowDef="displayCompanyListColumns; sticky: true"></tr>
            <tr mat-row *matRowDef="let row; columns: displayCompanyListColumns;" (click)="listClicked(row)"></tr>
          </table>
        </div>
      </div>
    </form>
  </div>
</div>

テストをするComponentのソースコードはこちら(company-list.component.ts)

import { merge, of } from 'rxjs';
import { catchError, map, startWith, switchMap } from 'rxjs/operators';
import { AppConst } from 'src/app/app-const';
import { SearchCompanyDto } from 'src/app/entity/company/search-company-dto';
import { CompanyService } from 'src/app/service/company/company.service';

import { HttpParams } from '@angular/common/http';
import { Component, OnInit, ViewChild } from '@angular/core';
import { FormBuilder, FormControl } from '@angular/forms';
import { MatPaginator } from '@angular/material/paginator';
import { Title } from '@angular/platform-browser';
import { Router } from '@angular/router';

@Component({
  selector: 'app-company-list',
  templateUrl: './company-list.component.html',
  styleUrls: ['./company-list.component.css']
})

export class CompanyListComponent implements OnInit {
  // Timezone and Locale
  locale: string;
  timezone: string;

  // Search criteria controls
  companyName = new FormControl('', []);
  companyKana = new FormControl('', []);
  deleted = new FormControl(false);

  // Form builder
  mainForm = this.formBuilder.group({
    companyName: this.companyName,
    companyKana: this.companyKana,
    deleted: this.deleted
  });

  // Search result dto
  searchCompanyDtos: SearchCompanyDto[];

  // Material tables header
  displayCompanyListColumns: string[] = [
    'companySeq',
    'companyName',
    'companyKana',
    'companyAddress1',
    'deleted',
    'createUser',
    'createTime',
    'updateUser',
    'updateTime',
  ];

  // Loading and pagenation
  isLoadingResults = false;
  resultsLength = 0;
  @ViewChild(MatPaginator, { static: true }) public paginator: MatPaginator;

  constructor(
    private formBuilder: FormBuilder,
    private companyService: CompanyService,
    private title: Title,
    private router: Router
  ) { }

  ngOnInit() {
    this.setUpLocale();
    this.setUpBrowserTitle();
  }

  /**
   * Sets the locale from appConst.
   */
  private setUpLocale() {
    this.locale = AppConst.LOCALE;
    this.timezone = AppConst.TIMEZONE;
  }

  /**
   * Sets screen title.
   */
  private setUpBrowserTitle() {
    this.title.setTitle(AppConst.APP_TITLE + AppConst.APP_SUB_TITLE_COMPANY_LIST);
  }

  /**
   * Clicks the new registration button.
   */
  private onNew() {
    this.router.navigate(['/company-detail/new']);
  }

  /**
   * Click the clear button.
   */
  private onClear() {
    this.clearSearchCondition();
    this.clearSearchResultList();
  }

  /**
   * Searches for customer informations.
   */
  private onSearch() {
    merge(this.paginator.page)
      .pipe(
        startWith({}),
        switchMap(() => {
          this.isLoadingResults = true;
          return this.companyService.getCompanyList(this.createHttpParams());
        }),

        map(data => {
          // Flip flag to show that loading has finished.
          this.isLoadingResults = false;
          this.resultsLength = data.resultsLength;
          this.paginator.pageIndex = data.pageIndex;
          return data.searchCompanyDtos;
        }),

        catchError(() => {
          this.isLoadingResults = false;
          return of(null as SearchCompanyDto[]);
        })

      ).subscribe(data => this.searchCompanyDtos = data);
  }

  /**
   * Creates search criterias.
   */
  private createHttpParams(): HttpParams {
    const conditions = {
      companyName: this.companyName.value,
      companyKana: this.companyKana.value,
      deleted: this.deleted.value.toString(),
      pageSize: this.paginator.pageSize.toString(),
      pageIndex: this.paginator.pageIndex.toString()
    };

    const paramsOptions = { fromObject: conditions };
    const params = new HttpParams(paramsOptions);

    return params;
  }

  /**
   * Clears search criteria controls.
   */
  private clearSearchCondition() {
    this.companyName.setValue('');
    this.companyKana.setValue('');
    this.deleted.setValue(false);
  }

  /**
   * Clears search result list.
   */
  private clearSearchResultList() {
    this.searchCompanyDtos = null;
    this.resultsLength = 0;
  }

  /**
   * Clicks search result.
   * @param searchCompanyDto cliked company entity.
   */
  private listClicked(searchCompanyDto: SearchCompanyDto) {
    this.router.navigate(['/company-detail', searchCompanyDto.companySeq]);
  }
}

基本的な DOM の操作方法

レファレンスを見る限り下記のような感じが良いかと思いました。
箇条書きにしてみます。

  1. const debugElement: DebugElement = fixture.debugElement;
    まずfixturedebugElement(Debug用のElement)を取得
    ちなみにこのfixturebeforeEachの中でTestBedのcreateComponent()メソッドから作られます。
  2. const queriedElement = debugElement.query(By.css(' id or class name' ));
    つぎにcssのセレクタを使用して目的の要素を取り出します。
  3. const htmlElement: HTMLElement = queriedElement.nativeElement;

実際に DOM を確認・操作したくなった時には、nativeElementプロパティを通して行います。

DOM のテストケースの追加

では実際にテストケースを追加してみます。

①初期表示のテスト

●検索条件の名前

まずは検索条件の名前をテストしてみます。
検索条件の部分はHTML中では下記のように書いています。
判定できそうなところがplaceholderしかありませんので、<div></div>タグ全体を取り出します。
そして<div></div>のテキストの中に日本語で "企業名" が含まれているかテストします。

<div id="searchCondition1">
  <mat-form-field class="form-field">
    <input id="companyName" matInput type="text" formControlName="companyName"
      placeholder="{{ 'companyListScreen.companyName' | translate }}">
  </mat-form-field>
</div>

下記がテストコードです。
<div></div>の個所はdebugElement.query(By.css('#searchCondition1'))で取得します。
そして最後にhtmlElement.textContentで中にあるテキスト文字を取得しテストします。
実際に動かすとテキスト文字は日本語にはなっておらず | translate より前の 'companyListScreen.companyName' が入ってました。
これは他言語化の為のtranslateサービスを spy にしていて、うまく日本語に変換されていない為と思われます。
やりたかったことと若干違いましたが、これでもテストにはなっているので今回はこれで良しとしました✨

it('should set company name with searchCondition1', () => {
  const debugElement: DebugElement = fixture.debugElement;
  const queriedElement = debugElement.query(By.css('#searchCondition1'));
  const htmlElement: HTMLElement = queriedElement.nativeElement;
  expect(htmlElement.textContent).toContain('companyListScreen.companyName');
});

●検索条件の初期値

次は検索条件それぞれの初期値です。
「企業名」「企業カナ」は初期値は空で、「削除済み」のチェックボックスはチェックOFFです。

it('verify initial value of searchConditions', () => {
  const debugElement: DebugElement = fixture.debugElement;
  let queriedElement = debugElement.query(By.css('#companyName'));
  let htmlInputElement: HTMLInputElement = queriedElement.nativeElement;
  expect(htmlInputElement.textContent).toEqual('');

  queriedElement = debugElement.query(By.css('#companyKana'));
  htmlInputElement = queriedElement.nativeElement;
  expect(htmlInputElement.textContent).toEqual('');

  queriedElement = debugElement.query(By.css('.mat-checkbox-inner-container'));
  htmlInputElement = queriedElement.nativeElement;
  expect(htmlInputElement.checked).toBeUndefined();
});

今回「削除済み」のチェックボックスのcssセレクタだけ、クラス指定(.mat-checkbox-inner-container)にしています。
これはAngular Materialのチェックボックスは、ブラウザで表示されたときに、書いたコードとは別のコードを生成して表示する為でした。
実際にわたしが書いたチェックボックスにはIDを振ったのですが、こちらが使えずしかたなくデバックで調べたクラスを指定しました。

f:id:tetsufuru:20190901174418p:plain Chromeでデバッグすると上記のように「mat-checkbox-inner-container」というクラスの要素が出来ています。

②初期表示以外のテスト

●検索条件の入力

次は検索条件に実際に入力してみます。
htmlInputElementの value に期待値をセットして、dispatchEvent(new Event('input'))で入力イベントを発生させます。
(入力イベントを発生させないと思ったように変数 companyName にバインドしないのでご注意ください)
そしてType Script側の変数 companyName に期待値が等しく入っているかをチェックしました。

it('should entry company name', () => {
  const debugElement: DebugElement = fixture.debugElement;
  const queriedElement = debugElement.query(By.css('#companyName'));
  const htmlInputElement: HTMLInputElement = queriedElement.nativeElement;
  const expectedEntry = 'abcd1234日本語';

  htmlInputElement.value = expectedEntry;
  htmlInputElement.dispatchEvent(new Event('input'));
  expect(component.companyName.value).toEqual(expectedEntry);
});

●HttpParamの値

最後は CompanyService の検索時に引数で渡すHttpParamに、検索条件の DOM の値が正しくセットされるかをテストします。
まず3種類の検索条件にそれぞれの期待値を入力します。
その後、createHttpParamsメソッドを実行してHttpParamが正しいかを検証します。

it('should create http param', () => {
  const debugElement: DebugElement = fixture.debugElement;

  let queriedElement = debugElement.query(By.css('#companyName'));
  const htmlInputElement: HTMLInputElement = queriedElement.nativeElement;
  const expectedEntry = 'abcd1234日本語';
  htmlInputElement.value = expectedEntry;
  htmlInputElement.dispatchEvent(new Event('input'));

  queriedElement = debugElement.query(By.css('#companyKana'));
  const htmlInputElementKana: HTMLInputElement = queriedElement.nativeElement;
  const expectedEntryKana = 'アイウエオカキクケコ';
  htmlInputElementKana.value = expectedEntryKana;
  htmlInputElementKana.dispatchEvent(new Event('input'));

  queriedElement = debugElement.query(By.css('.mat-checkbox-inner-container'));
  const htmlInputElementDeleted: HTMLInputElement = queriedElement.nativeElement;
  htmlInputElementDeleted.click();
  fixture.detectChanges();

  const conditions = {
    companyName: expectedEntry,
    companyKana: expectedEntryKana,
    deleted: 'true',
    pageSize: '50',
    pageIndex: '0',
  };
  const paramsOptions = { fromObject: conditions };
  const expectedHttpParams = new HttpParams(paramsOptions);

  expect(component['createHttpParams']()).toEqual(expectedHttpParams);
});

●テストコード全体

これまでのテストコード全体を下に記します、みなさまのご参考になればと思います。

テストコード(company-list.component.spec.ts)はこちら

import { throwError } from 'rxjs';
import { AppConst } from 'src/app/app-const';
import { HttpLoaderFactory } from 'src/app/app.module';
import { SearchCompanyDto } from 'src/app/entity/company/search-company-dto';
import { SearchCompanyListDto } from 'src/app/entity/company/search-company-list-dto';
import { CompanyService } from 'src/app/service/company/company.service';
import { asyncData } from 'src/app/testing/async-observable-helpers';
import { MaterialModule } from 'src/app/utils/material/material.module';

import { HttpClient, HttpClientModule, HttpParams } from '@angular/common/http';
import { DebugElement, NO_ERRORS_SCHEMA } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
import { By, Title } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { Router } from '@angular/router';
import { TranslateLoader, TranslateModule, TranslatePipe } from '@ngx-translate/core';

import { CompanyListComponent } from './company-list.component';

describe('CompanyListComponent', () => {
  let component: CompanyListComponent;
  let fixture: ComponentFixture;

  let routerSpy: { navigate: jasmine.Spy };
  let companyServiceSpy: { getCompanyList: jasmine.Spy };
  let translatePipeSpy: { translate: jasmine.Spy };

  beforeEach(async(() => {
    routerSpy = jasmine.createSpyObj('Router', ['navigate']);
    companyServiceSpy = jasmine.createSpyObj('CompanyService', ['getCompanyList']);
    translatePipeSpy = jasmine.createSpyObj('TranslatePipe', ['translate']);

    TestBed.configureTestingModule({
      declarations: [CompanyListComponent],
      schemas: [NO_ERRORS_SCHEMA],
      imports: [ReactiveFormsModule, BrowserAnimationsModule, MaterialModule, HttpClientModule,
        TranslateModule.forRoot({
          loader: {
            provide: TranslateLoader,
            useFactory: HttpLoaderFactory,
            deps: [HttpClient]
          }
        }),
      ],
      providers: [
        FormBuilder,
        Title,
        { provide: Router, useValue: routerSpy },
        { provide: CompanyService, useValue: companyServiceSpy },
        { provide: TranslatePipe, useValue: translatePipeSpy },
      ],
    })
      .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(CompanyListComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });

  /**
   * Type Script test cases.
   */
  it('locale and timezone should be set when called ngOnInit', () => {
    component.ngOnInit();
    expect(component.locale).toEqual(AppConst.LOCALE);
    expect(component.timezone).toEqual(AppConst.TIMEZONE);
  });

  it('locale and timezone should be set when called setUpLocale', () => {
    component['setUpLocale']();
    expect(component.locale).toEqual(AppConst.LOCALE);
    expect(component.timezone).toEqual(AppConst.TIMEZONE);
  });

  // TBD
  // it('browser title should be set when called setUpBrowserTitle', () => {
  //   component['setUpBrowserTitle']();
  //   expect(component.locale).toEqual(AppConst.LOCALE);
  // });

  it('should navigate when called onNew', () => {
    component['onNew']();
    expect(routerSpy.navigate.calls.count()).toBe(1, 'one call');
  });

  it('should navigate when called onClear', () => {
    fillSearchCriteria(component);
    component['onClear']();
    expect(component.companyName.value).toEqual('');
    expect(component.companyKana.value).toEqual('');
    expect(component.deleted.value).toEqual(false);
  });

  it('should call map operator when called onSearch', async () => {
    const expectedSearchCompanyListDto: SearchCompanyListDto = new SearchCompanyListDto();
    const searchCompanyDto: SearchCompanyDto[] =
      [{
        companySeq: BigInt('1'),
        companyName: 'companyName',
        companyKana: 'companyKana',
        companyAddress1: 'companyAddress1',
        deleted: '',
        createUser: 'createUser',
        createTime: new Date,
        updateUser: 'updateUser',
        updateTime: new Date
      }];
    expectedSearchCompanyListDto.searchCompanyDtos = searchCompanyDto;
    companyServiceSpy.getCompanyList.and.returnValue(asyncData(expectedSearchCompanyListDto));

    await component['onSearch']();
    expect(component.searchCompanyDtos).toEqual(expectedSearchCompanyListDto.searchCompanyDtos);
    expect(component.isLoadingResults).toEqual(false);
    expect(companyServiceSpy.getCompanyList.calls.count()).toBe(1, 'one call');
  });

  it('should catch error when called onSearch', async () => {
    await component['onSearch']();
    companyServiceSpy.getCompanyList.and.returnValue(throwError(''));
    expect(component.isLoadingResults).toEqual(false);
  });

  it('should navigate when called listClicked', () => {
    const searchCompanyDto: SearchCompanyDto = new SearchCompanyDto();
    searchCompanyDto.companySeq = BigInt('1');
    component['listClicked'](searchCompanyDto);
    expect(routerSpy.navigate.calls.count()).toBe(1, 'one call');
  });

  /**
   * DOM test cases.
   */
  it('should set company name with searchCondition1', () => {
    const debugElement: DebugElement = fixture.debugElement;
    const queriedElement = debugElement.query(By.css('#searchCondition1'));
    const htmlElement: HTMLElement = queriedElement.nativeElement;
    expect(htmlElement.textContent).toContain('companyListScreen.companyName');
  });

  it('should set company kana with searchCondition2', () => {
    const debugElement: DebugElement = fixture.debugElement;
    const queriedElement = debugElement.query(By.css('#searchCondition2'));
    const htmlElement: HTMLElement = queriedElement.nativeElement;
    expect(htmlElement.textContent).toContain('companyListScreen.companyKana');
  });

  it('should set deleted with searchCondition3', () => {
    const debugElement: DebugElement = fixture.debugElement;
    const queriedElement = debugElement.query(By.css('#searchCondition3'));
    const htmlElement: HTMLElement = queriedElement.nativeElement;
    expect(htmlElement.textContent).toContain('companyListScreen.deleted');
  });

  it('verify initial value of searchConditions', () => {
    const debugElement: DebugElement = fixture.debugElement;
    let queriedElement = debugElement.query(By.css('#companyName'));
    let htmlInputElement: HTMLInputElement = queriedElement.nativeElement;
    expect(htmlInputElement.textContent).toEqual('');

    queriedElement = debugElement.query(By.css('#companyKana'));
    htmlInputElement = queriedElement.nativeElement;
    expect(htmlInputElement.textContent).toEqual('');

    queriedElement = debugElement.query(By.css('.mat-checkbox-inner-container'));
    htmlInputElement = queriedElement.nativeElement;
    expect(htmlInputElement.checked).toBeUndefined();
  });

  it('should entry company name', () => {
    const debugElement: DebugElement = fixture.debugElement;
    const queriedElement = debugElement.query(By.css('#companyName'));
    const htmlInputElement: HTMLInputElement = queriedElement.nativeElement;
    const expectedEntry = 'abcd1234日本語';

    htmlInputElement.value = expectedEntry;
    htmlInputElement.dispatchEvent(new Event('input'));
    expect(component.companyName.value).toEqual(expectedEntry);
  });

  it('should entry company kana', () => {
    const debugElement: DebugElement = fixture.debugElement;
    const queriedElement = debugElement.query(By.css('#companyKana'));
    const htmlInputElement: HTMLInputElement = queriedElement.nativeElement;
    const expectedEntry = 'アイウエオカキクケコ';

    htmlInputElement.value = expectedEntry;
    htmlInputElement.dispatchEvent(new Event('input'));
    expect(component.companyKana.value).toEqual(expectedEntry);
  });

  /** The material check box is different from normal. */
  it('should entry deleted', () => {
    const debugElement: DebugElement = fixture.debugElement;
    const queriedElement = debugElement.query(By.css('.mat-checkbox-inner-container'));
    const htmlInputElement: HTMLInputElement = queriedElement.nativeElement;

    htmlInputElement.click();
    fixture.detectChanges();
    expect(component.mainForm.value.deleted).toBe(true);

  });

  it('should create http param', () => {
    const debugElement: DebugElement = fixture.debugElement;

    let queriedElement = debugElement.query(By.css('#companyName'));
    const htmlInputElement: HTMLInputElement = queriedElement.nativeElement;
    const expectedEntry = 'abcd1234日本語';
    htmlInputElement.value = expectedEntry;
    htmlInputElement.dispatchEvent(new Event('input'));

    queriedElement = debugElement.query(By.css('#companyKana'));
    const htmlInputElementKana: HTMLInputElement = queriedElement.nativeElement;
    const expectedEntryKana = 'アイウエオカキクケコ';
    htmlInputElementKana.value = expectedEntryKana;
    htmlInputElementKana.dispatchEvent(new Event('input'));

    queriedElement = debugElement.query(By.css('.mat-checkbox-inner-container'));
    const htmlInputElementDeleted: HTMLInputElement = queriedElement.nativeElement;
    htmlInputElementDeleted.click();
    fixture.detectChanges();

    const conditions = {
      companyName: expectedEntry,
      companyKana: expectedEntryKana,
      deleted: 'true',
      pageSize: '50',
      pageIndex: '0',
    };
    const paramsOptions = { fromObject: conditions };
    const expectedHttpParams = new HttpParams(paramsOptions);

    expect(component['createHttpParams']()).toEqual(expectedHttpParams);
  });

});
function fillSearchCriteria(component: CompanyListComponent) {
  component.companyName.setValue('a');
  component.companyKana.setValue('a');
  component.deleted.setValue(true);
}

今日の感想


今日はComponentのテストその2ということで、DOM 側のテストについて書きました。
ひととおり完成したと思いブログを書き始めたのですが、書き終わる今になって、
検索結果の一覧のテストを追加したほうが良い気もしてきました。
Jasmineをはじめて間もないので、もっと良いやり方があればまだまだ工夫していきたいと思います。
少し落ち着いたらそのうちまたJasmineについて記事を書きたいと思います。

では、今日もお疲れ様でした。