Search code examples
unit-testinginheritancejunitinstancio

How can I create an Instancio Model of a subtype, using the configuration of a model of it's supertype?


Does anyone have experience with creating an Instancio Model subtype using the properties of a Model supertype is possible? I tried many different ways eventually ending up with the example code, but I can't get it to work. Am I trying to do something that is just not possible? This feature sounds so obvious to me that I cannot imagine that it wouldn't be possible. But at the very least it's very hard to do and I couldn't find documentation about it.

I'm using Instancio 5.4.0 but this code is giving me this error:

Found unused selectors referenced in the following methods:

  • Unused selector in: set()
    • 1: field(Pet, "color")
    • 2: field(Pet, "name")

This is the example code that I am using.

import org.instancio.Instancio;
import org.instancio.Model;
import org.instancio.Select;
import org.instancio.settings.Settings;
import org.junit.jupiter.api.Test;

public class ModelInheritance {

    @Test
    void modelTest() {
        Dog dog = Instancio.create(defaultDogModel());
        System.out.println(dog.getSound());
    }

    private Model<Dog> defaultDogModel() {
        return Instancio.of(Dog.class)
                .withSettings(Settings.create().mapType(Pet.class, Dog.class))
                .set(Select.fields().ofType(Pet.class), Instancio.create(defaultPetModel()))
                .set(Select.field(Dog::getSound), "Bark")
                .toModel();
    }

    private Model<Pet> defaultPetModel() {
        return Instancio.of(Pet.class)
                .set(Select.field(Pet::getName), "Punky")
                .set(Select.field(Pet::getColor), "White")
                .toModel();
    }

}

abstract class Pet {
    String name;
    String color;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }
}

class Dog extends Pet {
    String sound;

    public String getSound() {
        return sound;
    }

    public void setSound(String sound) {
        this.sound = sound;
    }
}


Solution

  • I think the problem is that Select.fields().ofType(Pet.class) expects an actual field of type Pet, but since there's no such field in the Dog class, it fails. You can replace it with a class selector instead. Any of these should work:

    • Select.types().of(Pet.class)
    • Select.all(Pet.class)

    You can also use Select.root() because the root object Dog extends Pet.

    Since you're using a Model, you might be better off using the setModel() instead of set(). Here's an example:

    private Model<Dog> defaultDogModel() {
        return Instancio.of(Dog.class)
                .withSettings(Settings.create().mapType(Pet.class, Dog.class))
                .setModel(Select.types().of(Pet.class), defaultPetModel()) // use setModel()
                .set(Select.field(Pet::getName), "Fido") // e.g. override name from the model
                .set(Select.field(Dog::getSound), "Bark")
                .toModel();
    }
    
    private Model<Pet> defaultPetModel() {
        return Instancio.of(Pet.class)
                // mark as lenient() if you're going to override these selectors
                .set(Select.field(Pet::getName).lenient(), "Punky")
                .set(Select.field(Pet::getColor).lenient(), "White")
                .toModel();
    }