Android テスト追加して実行する手順

書くほどのことじゃないかもしれませんが、都度忘れちゃいそうなので書いておきます。

Android Studioのバージョンは次の通り

Android Studio Koala | 2024.1.1 Patch 1

①下記の app というところをクリック
下に出てくるEdit Configurations… をクリック

②下記の画面で左上の小さいプラスマークをクリックします。

③InstrumentedTestをやりたい場合は下に出てくるAndroid Instrumented Testsをクリック

下記の画面になるので、Moduleのところをクリックし、テストのあるディレクトリをクリック

④ローカルテストをやりたい場合は②の後 JUnitを選択し、下記のように入力します。

その後、Runというボタンをクリックで、テストが実行されます。

ちなみに、「ローカルテスト」と「インストルメンテーション テスト」 って何が違うの?という方は、下記のページをご覧下さい。

https://developer.android.com/training/testing/fundamentals?hl=ja

ちなみに、ローカルテストはファイルを右クリックでRunとかすると、下記のエラーが出て動作できませんでした。
上記のやり方で実行できるのであまり掘り下げていませんが、詳しい方がいれば教えてください。┌o ペコッ

   Task :app:testDebugUnitTest FAILED
エラー: メイン・クラスworker.org.gradle.process.internal.worker.GradleWorkerMainを検出およびロードできませんでした
原因: java.lang.ClassNotFoundException: worker.org.gradle.process.internal.worker.GradleWorkerMain
FAILURE: Build failed with an exception.
What went wrong:
Execution failed for task ':app:testDebugUnitTest'.
Process 'Gradle Test Executor 2' finished with non-zero exit value 1


Android Parcel の読出しに関連するエラー java.lang.RuntimeException: Parcel android.os.Parcel: Unmarshalling unknown type…

Android Parcel、Parcelableに関連するエラーにかなりはまったので書いておきます。


FATAL EXCEPTION: main
java.lang.RuntimeException: Unable to start activity ComponentInfo{hogehoge.HogeActivity}: java.lang.RuntimeException: Parcel android.os.Parcel@24940f: Unmarshalling unknown type code 7602259 at offset 2164
	at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3663)
	at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3820)
	at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:103)
	at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
	at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
	at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2218)
	at android.os.Handler.dispatchMessage(Handler.java:106)
	at android.os.Looper.loopOnce(Looper.java:201)
	at android.os.Looper.loop(Looper.java:288)
	at android.app.ActivityThread.main(ActivityThread.java:8001)
	at java.lang.reflect.Method.invoke(Native Method)
	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1039)
Caused by: java.lang.RuntimeException: Parcel android.os.Parcel@24940f: Unmarshalling unknown type code 7602259 at offset 2164
	at android.os.Parcel.readValue(Parcel.java:3306)
	at android.os.Parcel.readArrayMapInternal(Parcel.java:3624)
	at android.os.BaseBundle.initializeFromParcelLocked(BaseBundle.java:292)
	at android.os.BaseBundle.unparcel(BaseBundle.java:236)
	at android.os.BaseBundle.getBoolean(BaseBundle.java:898)

上記のようなエラーが出てしまいます。

細かい説明は割愛しますが、例えばAndroidの画面をまたいで複雑なオブジェクトをやり取りしたい時に、このParcel、Parcelableという仕組みを使います。

Parcelableというインターフェースをimplementsすると、そのクラスがParcelとして扱えるようになります。

例えば次のように作るのですが

public class Hoge implements Parcelable {

    private final int id;
    private final String name;

    public Hoge(int id, String name) {
        this.id = id;
        this.name = name;
    }

    protected Hoge(Parcel in) { 
        id = in.readInt(); // ★この順番と
        name = in.readString();
    }

    @Override
    public void writeToParcel(@NonNull Parcel dest, int flags) {
        dest.writeInt(id); // ★ここの順番は一緒でなくてはならない
        dest.writeString(name);
    }

    public static final Creator<Hoge> CREATOR = new Creator<Hoge>() {
        @Override
        public Hoge createFromParcel(Parcel in) {
            if(in.readInt() == 1) {
                return new Hoge2(in);
            }
            return new Hoge(in);
        }

        @Override
        public Hoge[] newArray(int size) {
            return new Hoge[size];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

}


Parcel inが引数のコンストラクタがParcelを読みだしてコンストラクトします。
writeToParcel()というメソッドで、Parcelに書き出しをします。

あんましここのイメージわきにくかったんですが、BlackHatのYoutubeさんにちょうどよい画像を見つけました。

Android Parcelイメージ
Android Parcelのイメージ

下記からの引用になります。ありがとうございます。
https://www.youtube.com/watch?v=qIzMKfOmIAA&list=LL&index=1

こういう構造だから、読みだしと書き込みが一緒じゃないとダメなんですね。
絵にするとめちゃわかりやすいです!( ˊᵕˋ )

サンプルコードではこの★がついたところが読みだしと書き出しにあたります。

冒頭のエラー はこれが順番通りじゃないと読出しに失敗して起こるエラーだそうです。

何度も目をこすりながら確認しても読出しと書き出しは一緒。
ChatGPTに聞いてもわかりませんでした。

結局、理由はこの部分↓にありました。

    public static final Creator<Hoge> CREATOR = new Creator<Hoge>() {
        @Override
        public Hoge createFromParcel(Parcel in) {
            if(in.readInt() == 1) { // 💀 問題はここだった
                return new Hoge2(in);
            }
            return new Hoge(in);
        }

        @Override
        public Hoge[] newArray(int size) {
            return new Hoge[size];
        }
    };

in.readInt() == 1 で、in の1つ目が使われている、ということみたいです。

この部分をほかに移すことでなんとかなりました。

Creatorの中も注意!ということです。

GSON のError java.lang.IllegalArgumentException: class declares multiple JSON fields named ..

val gson = Gson().toJson(hoge)

でJson化をしたときに、下記のようなエラーが出ることがあります。

java.lang.IllegalArgumentException: class hoge.Hoge declares multiple JSON fields named start_time
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:172)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:102)
at com.google.gson.Gson.getAdapter(Gson.java:458)
at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:56)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.write(ReflectiveTypeAdapterFactory.java:127)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.write(ReflectiveTypeAdapterFactory.java:245)
at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:69)
at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.write(CollectionTypeAdapterFactory.java:97)
at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.write(CollectionTypeAdapterFactory.java:61)
at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:69)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.write(ReflectiveTypeAdapterFactory.java:127)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.write(ReflectiveTypeAdapterFactory.java:245)
at com.google.gson.Gson.toJson(Gson.java:704)
at com.google.gson.Gson.toJson(Gson.java:683)
at com.google.gson.Gson.toJson(Gson.java:638)
at com.google.gson.Gson.toJson(Gson.java:618)

え??なんで?なんでなん?

てなりますが、原因は、例えばこれはHogeというクラスのオブジェクトをJSON化していますが、そのHogeのスーパークラスSuperHogeがあったとして、同じプロパティを宣言していると、これが出ます。

例は次のような感じです。

public class SuperHoge {
    protected String name;
}

class Hoge extends SuperHoge{
    private String name;
}

No overload matches this call. Overload 1 of 3, ‘(object: Hoge, method: never): SpyInstance‘, gave the following error.

Jestのバージョン 29.5.3

class Hoge{

    private validate(start_unix: number, end_unix: number, ids: (number | string)[]): boolean {

	    // 色々バリデーションの処理
	    return true

    }

}

Hogeクラスにvalidate()というメソッドがありますが、テストだとこのバリデーションを使いたくないという場合、validateのモックを作る場合があると思います。

テストコードは次の通り

test('Hoge without validate', () => {
 const hoge = new Hoge()
 const spy = jest.spyOn(hoge, 'validate')
  .mockImplementation(() => true);
}

しかし、これだと次のようなエラーが出てしまいます。

No overload matches this call.       Overload 1 of 3, '(object: Hoge, method: never): SpyInstance', gave the following error.

なんか引数とかの指定が悪いのかな?とか思ってしまいますが、これはspyOnの対象のメソッドがprivateだとこのエラーになるみたいです。

「じゃー、対象のメソッドを public にしよう!」

ではなくて、めちゃ簡単にこれは解決できます。

jest.spyOn()の1個目の引数で呼び出すインスタンスを、as any で呼べばよいです。

 const spy = jest.spyOn(hoge as any, 'validate')
  .mockImplementation(() => true);

下記で教えて頂きました。┌o ペコッ ありがたい!

https://stackoverflow.com/questions/62171602/testing-private-method-using-spyon-and-jest


社員旅行で行った台湾で見かけた派手な百合

jestでgetActivePinia()” was called but there was no active Pinia.

Vue(バージョン2.7)+Jest(バージョン29)+Pinia(バージョン2.1)でJestでテストしていると、次のようなエラーになってしまいました。

import { mount } from '@vue/test-utils'
import DestinationModal from "../../../components/location/poi/organisms/DestinationModal.vue";


describe('DestinationModal', () => {
test('is a Vue instance', () => {
const wrapper = mount(DestinationModal)
expect(wrapper.isVueInstance()).toBeTruthy()
})
})

実行すると

Error: [🍍]: "getActivePinia()" was called but there was no active Pinia. Are you trying to use a store before calling "app.use(pinia)"?

Piniaがないのでエラーになってしまうので

import { mount } from '@vue/test-utils'
import DestinationModal from "../../../components/location/poi/organisms/DestinationModal.vue";
import {createPinia, setActivePinia} from "pinia" //これを追加


describe('DestinationModal', () => {
setActivePinia(createPinia())
//これを追加
test('is a Vue instance', () => {
const wrapper = mount(DestinationModal)
expect(wrapper.isVueInstance()).toBeTruthy()
})
})

パイナップルがかわいいですね!🍍