Mockito実行時エラーの対処法

Mockito実行時エラーの対処法を随時更新していきます。

Checked exception is invalid for this method!

実行コード

doThrow(new Exception("test")).when(hoge).fuga(); 

エラーログ

org.mockito.exceptions.base.MockitoException: 
Checked exception is invalid for this method!
Invalid: java.lang.Exception: test

原因と対処法

// NG
// 検査例外(Exception系)のthrows宣言がないメソッドに対して、doThrowで検査例外をスローできない
doThrow(new Exception("test")).when(hoge).fuga();

// OK
// 非検索例外(RuntimeException系)ならスローできる
doThrow(new RuntimeException(new Exception("test"))).when(hoge).fuga();

参考 Android開発するときにテスト絡みで調べたことまとめ - リア充爆発日記

グループごとの最新レコードを取得するSQL

仕事でグループごとの最新レコードを取得するSQLを書く機会がありました。
グループごとの最新レコードを取得する方法は色々ありますが、今回は分析関数(ROW_NUMBER)を使用してみました。

グループごとの最新レコードを取得する方法

調べてみるとグループごとの最新レコードを取得する方法は色々あるようです。
特に参考になったのは以下です。

集約関数(MAX)やNOT EXISTSを使う方法
同一グループの中で最大のレコードを取得する SQL を書く - TIM Labs

GROUP BYや分析関数(ROW_NUMBER)を使う方法
SQLでグループごとにある最大値の行を取得する - Qiita

分析関数とは

分析関数を使う方法がありましたが、そもそも分析関数に馴染みがなかったので調べました。

分析関数(ROW_NUMBER・RANK・DENSE_RANK)は連番や順位を返すSQL関数です。
JOIN・WHERE・GROUP BY・HAVING句が実行された結果に対して分析関数が実行されるので、分析関数が使える箇所はSELECT・ORDER BY句になります。
分析関数によって取得レコードが集約されることはありません。

分析関数の構文は以下になります。

分析関数 OVER (PARTITION BY 集計単位 ORDER BY 表示順) 

ROW_NUMBER()

1から始まる連番を返します。
同値の場合の順番は不定です。
順番を一定にしたい場合はソート条件にユニークキーを追加する必要があります。

RANK()

1から始まる順位を返します。
同値の場合は同順位で、次の順位は飛ばされます。

DENSE_RANK()

1から始まる順位を返します。
同値の場合は同順位で、次の順位は飛ばされません。

OVER句

分析関数や集約関数で使用できます。
PARTITION BY句で集計単位を指定します。
ORDER BY句でソート順を指定します。
PARTITION BY・ORDER BY句はそれぞれ省略可能で、OVER句自体も省略可能です。

分析関数でSQLを作成してみた

今回作成したSQLの仕様は以下です。

  • グループごとで更新日時が最新のレコードを抽出する
  • グルーピングするキーは2つある
  • 更新日時はユニークではない
  • 更新日時が同値のレコードが複数存在する場合、その中で任意のレコードを抽出する

グループキーが複数あり、最新レコードも複数存在しうるケースだったため、分析関数を使うのが簡単かと思いました。
とういうか、グループごとの最新レコードを取得したい場合は分析関数を使うのが一番楽な気がします。

上記の仕様を実装したSQLのサンプルが以下になります。
データベースはOracleを使用しています。

SELECT
  順位付けしたテーブル.取得したいカラム1
  , 順位付けしたテーブル.取得したいカラム2
  , 順位付けしたテーブル.取得したいカラム3
FROM
  (
    SELECT
      取得したいカラム1
      , 取得したいカラム2
      , 取得したいカラム3
      , ROW_NUMBER() OVER ( PARTITION BY 集計したいカラム1 , 集計したいカラム2 ORDER BY 更新日時 DESC ) AS 順位
    FROM
      対象テーブル
    WHERE
      検索したいカラム1 = '検索条件1'
      AND 検索したいカラム2 = '検索条件2' 
      AND 検索したいカラム3 = '検索条件3' 
  ) 順位付けしたテーブル
WHERE
  順位付けしたテーブル.順位 = 1

参考
ROW_NUMBER,RANK,DENSE_RANK 行番号や順位を返すSQL関数

分析関数 ROW_NUMBER の使用例 - オラクル・Oracleをマスターするための基本と仕組み

SQL PARTITION BYの基本と効率的に集計する便利な方法

SQL - グルーピング, 集約関数
https://creasys.org/rambo/articles/9a3caec10b8c21e45671

Mockitoの使い方とTips

MockitoはJava用のモックライブラリで、JUnitユニットテスト)を簡単にします。
今のプロジェクトでもMockitoを使用しているので、使い方やTipsをまとめたいと思います。

Mockitoの使い方

Mockitoの使い方は以下が参考になります。

Mockito事始め - 俄

リダイレクトの警告

MockitoのTips

Mockitoで嵌りやすいところをTipsとしてまとめました。
使用しているMockitoのバージョンは1.10.19です。

thenReturn 対 doReturn

メソッドをモック化する方法はthenXX系(thenReturnやthenThrowなど)とdoXX系(doReturnやdoThrowなど)の2通りがあります。
どちらを使うかですが、doXX系を使った方が良いかと思います。
なぜなら、thenXX系では使用できないケースがあるからです。

thenXX系が使用できないケース

  • voidメソッドの場合
  • spy(@Spy、Mockito.spy())でモック化したオブジェクトの場合

thenXX系の利点はコンパイル時に引数の型チェックをしてくれることです。
しかし、テストクラスは実行しながら作成するので、型チェックの恩恵はそこまで感じませんでした。
thenXX系とdoXX系を使い分けるよりもdoXX系に統一した方が楽です。

// 構文
Mockito.when(クラス名.メソッド名(引数)).thenReturn(戻り値);
Mockito.doReturn(戻り値).when(クラス名).メソッド名(引数);

// 例
Mockito.when(Hoge.fuga("foo")).thenReturn("bar");
Mockito.doReturn("bar").when(Hoge).fuga("foo");

参考
Mockito (Mockito 2.23.4 API)

リダイレクトの警告

リダイレクトの警告

Matcherと通常引数の併用

Matcher(anyXX系)は任意の引数を指定する場合に便利です。

// 引数が"foo"の場合に"bar"を返す
Mockito.doReturn("bar").when(Hoge).fuga("foo");

// 引数が任意の文字列の場合に"bar"を返す
Mockito.doReturn("bar").when(Hoge).fuga(anyString());

ただし、Matcherを使った引数と通常の引数を併用することはできないので、通常の引数はeq()で囲ってMatcherに変換する必要があります。

// NG(実行時エラー)
Mockito.doReturn("bar").when(Hoge).fuga(anyString(), "foo");

// OK
Mockito.doReturn("bar").when(Hoge).fuga(anyString(), eq("foo"));

参考
Mockitoの新機能を使ったモダンげな使い方 - 愛と勇気と缶ビール

モックで本物のメソッドを呼ぶ方法

特定のメソッドだけ本物のメソッドを呼び出したい場合があるかと思います。
その場合はspyもしくはthenCallRealMethodが使えます。

spy
spy(@Spy, Mockito.spy())でモック化したオブジェクトは本物のメソッドを呼び出します。
doXXX系を使うことで、特定のメソッドをモック化することができます。

thenCallRealMethod()
mock(@Mock, Mockito.mock())でモック化したオブジェクトに対して、thenCallRealMethod()を指定することで、本物のメソッドを呼び出すことができます。

// fugaメソッドは本物が呼ばれる
Mockito.when(Hoge.fuga("foo")).thenCallRealMethod();

参考
テックノート – Junitライブラリ「Mockito」の@Spyの使い方

テックノート – Junitライブラリ「Mockito」で一部本物のメソッドを使う方法(thenCallRealMethod)

Next Action

@MockBeanの使い方

@Mockと@MockBeanの使い分けがよく分からなかったので、分かり次第追記します。

参考
Spring BootでAutowiredされるクラスをMockitoでモックする - Qiita

Mockito.mock() vs @Mock vs @MockBean | Baeldung

Mockito実行時エラーの対処法

Mockitoで実行時エラーとなった場合にエラーの原因を特定するのが難しいことが度々ありました。
Mockitoの実行時エラーと対処法を発生ベースでまとめていきたいと思います。  

ブログ初めました

初めまして、とっきーです。

2019年1月よりブログを始めました。

 

職業がSE(システムエンジニア)なので、IT技術系のネタを中心に書いていこうと思います。

 

SEとしてまだまだ未熟者ですので、記事の内容に間違いや改善点などありましたら、指摘して頂けると幸いです。

 

このブログを始めた理由としては

  • 勉強した技術をストックする
  • アウトプットする力を高める

ですので、基本的に自分のためのブログですが、このブログが少しでも誰かの役に立つことを願っています。

 

それでは、宜しくお願いいたします。