커스터마이징 API
Fixture Monkey는 ArbitraryBuilder를 통해 생성된 객체를 커스텀할 수 있는 다양한 API를 제공합니다.
픽스쳐 커스터마이징하기
set()
set()
메서드는 표현식에 참조된 하나 이상의 프로퍼티에 값을 설정하는 데 사용됩니다.
Supplier
, Arbitrary
, ArbitraryBuilder
, NOT_NULL
, NULL
, 또는 Just
를 포함한 다양한 타입을 값으로 설정할 수 있습니다.
또한 객체의 특정 인스턴스를 값으로 사용할 수도 있습니다.
fixtureMonkey.giveMeBuilder(Product.class)
.set("id", 1000);
fixtureMonkey.giveMeBuilder<Product>()
.setExp(Product::id, 1000)
Just
set()
을 사용할 때Just
로 래핑된 객체를 사용하면 인스턴스를 분해하지 않고 값을 직접 설정할 수 있습니다. 일반적으로ArbitraryBuilder
에서 프로퍼티를set()
하면 주어진 인스턴스를 그대로 사용하지 않고 깊은 복사를 수행합니다. 따라서 인스턴스로 설정해야 하는 경우Values.just(instance)
를 사용해야 합니다. 이 기능은 Mocking 프레임워크를 사용할 때 Mock 인스턴스에 프로퍼티를 설정해야 하는 경우 유용합니다.
Just
로 설정한 후에는 하위 속성을 변경할 수 없으니 유의하세요.
Product product = fixture.giveMeBuilder(Product.class)
.set("options", Values.just(List.of("red", "medium", "adult"))
.set("options[0]", "blue")
.sample();
예를 들어, 위에서 생성된 Product 인스턴스의 options[0] 값은 “blue” 가 아닌
Just
로 설정된 리스트로 유지됩니다.
size(), minSize(), maxSize()
size()
메서드를 사용하면 컨테이너 프로퍼티의 크기를 지정할 수 있습니다.
정확한 크기를 설정하거나 최소값과 최대값을 사용하여 범위를 지정하는 등 유연하게 사용할 수 있습니다.
혹은 minSize()
또는 maxSize()
를 사용하여 최소 또는 최대 컨테이너 크기만 설정할 수도 있습니다. (디폴트 설정은 0 ~ 3 입니다.)
fixtureMonkey.giveMeBuilder(Product.class)
.size("options", 5); // size:5
fixtureMonkey.giveMeBuilder(Product.class)
.size("options", 3, 5); // minSize:3, maxSize:5
fixtureMonkey.giveMeBuilder(Product.class)
.minSize("options", 3); // minSize:3
fixtureMonkey.giveMeBuilder(Product.class)
.maxSize("options", 5); // maxSize:5
fixtureMonkey.giveMeBuilder<Product>()
.sizeExp(Product::options, 5) // size:5
fixtureMonkey.giveMeBuilder<Product>()
.sizeExp(Product::options, 3, 5) // minSize:3, maxSize:5
fixtureMonkey.giveMeBuilder<Product>()
.minSizeExp(Product::options, 3) // minSize:3
fixtureMonkey.giveMeBuilder<Product>()
.maxSizeExp(Product::options, 5) // maxSize:5
setNull(), setNotNull()
때로는 속성을 항상 null로 설정하거나 항상 값이 존재하도록 보장하고 싶을 수 있습니다.
이러한 상황에서는 setNull()
또는 setNotNull()
을 사용할 수 있습니다.
fixtureMonkey.giveMeBuilder(Product.class)
.setNull("id");
fixtureMonkey.giveMeBuilder(Product.class)
.setNotNull("id");
fixtureMonkey.giveMeBuilder<Product>()
.setNullExp(Product::id)
fixtureMonkey.giveMeBuilder<Product>()
.setNotNullExp(Product::id)
setInner()
setInner()
를 사용하면 InnerSpec
인스턴스에 정의된 커스텀을 빌더에 적용할 수 있습니다.
InnerSpec
은 타입에 독립적으로 사용 가능한 커스텀 명세입니다.
InnerSpec
인스턴스를 재사용하여 중첩 프로퍼티를 일관되고 쉽게 구성할 수 있습니다.
특히 Map의 속성을 커스텀할 때 유용합니다.
자세한 내용은 InnerSpec 을 참고하세요.
InnerSpec innerSpec = new InnerSpec()
.property("merchantInfo", it -> it.entry(1000, "ABC Store"));
fixtureMonkey.giveMeBuilder(Product.class)
.setInner(innerSpec)
val innerSpec = InnerSpec()
.property("merchantInfo") { it.entry(1000, "ABC Store") }
fixtureMonkey.giveMeBuilder(Product.class)
.setInner(innerSpec)
setLazy()
The setLazy()
함수는 Supplier에서 얻은 값을 프로퍼티에 할당합니다.
이 Supplier은 ArbitraryBuilder가 샘플링(sample()
)될 때마다 실행됩니다.
이 함수는 고유한 순차 ID를 생성하거나 가장 최근 값으로 설정해야 할 때 특히 유용합니다.
AtomicReference<Long> variable = new AtomicReference<>(0L);
ArbitraryBuilder<Long> builder = fixtureMonkey.giveMeBuilder(Long.class)
.setLazy("$", () -> variable.getAndSet(variable.get() + 1));
Long actual1 = builder.sample(); // actual1 == 0
Long actual2 = builder.sample(); // actual2 == 1
var variable = 0L
val builder = fixtureMonkey.giveMeBuilder(Long::class.java)
.setLazy("$") { variable++ }
val actual1 = builder.sample() // actual1 == 0
val actual2 = builder.sample() // actual2 == 1
setPostCondition()
setPostCondition()
은 픽스처가 특정 조건을 준수해야 할 때 사용할 수 있습니다.
이 조건은 predicate를 전달하여 정의할 수 있습니다.
fixtureMonkey.giveMeBuilder(Product.class)
.setPostCondition("id", Long.class, it -> it > 0)
fixtureMonkey.giveMeBuilder(Product::class.java)
.setPostConditionExp(Product::id, Long::class.java) { it: Long -> it > 0 }
fixed()
fixed()
를 사용하면, ArbitraryBuilder가 샘플링될 때마다 동일한 값을 가진 인스턴스를 반환합니다.
fixtureMonkey.giveMeBuilder(Product.class)
.fixed()
fixtureMonkey.giveMeBuilder<Product>()
.fixed()
limit
set()
, setLazy()
, 및 setPostCondition()
메서드는 추가 매개변수를 통해 커스텀을 적용할 횟수를 제한할 수 있습니다.
표현식이 여러 프로퍼티를 참조하는 경우에 특히 유용합니다.
fixtureMonkey.giveMeBuilder(Product.class)
.set("options[*]", "red", 2); // options에 "red"는 2개까지만 설정될 수 있습니다.
fixtureMonkey.giveMeBuilder<Product>()
.set("options[*]", "red", 2) // options에 "red"는 2개까지만 설정될 수 있습니다.
샘플링 결과를 활용해 추가 커스터마이징하기
thenApply()
thenApply()
메서드는 빌더의 샘플링된 결과를 기반으로 필드를 커스텀해야 할 때 편리합니다.
예를 들어, 다음과 같이 thenApply()
를 사용해 “productName” 필드를 생성된 Product의 “id"와 일치하도록 설정할 수 있습니다.
fixtureMonkey.giveMeBuilder(Product.class)
.thenApply((it, builder) -> builder.set("productName", it.getId().toString()))
fixtureMonkey.giveMeBuilder(Product::class.java)
.thenApply{it, builder -> builder.setExp(Product::productName, it.id.toString())}
acceptIf()
특정 조건에 따라 추가 커스텀을 수행해야 할 수도 있습니다.
이러한 경우 predicate가 충족될 때만 커스텀을 적용하는 acceptIf()
메서드를 활용할 수 있습니다.
fixtureMonkey.giveMeBuilder(Product.class)
.acceptIf(
it -> it.getProductType() == ProductType.CLOTHING,
builder -> builder.set("price", 1000)
)
fixtureMonkey.giveMeBuilder<Product>()
.acceptIf(
{ it.productType == ProductType.CLOTHING },
{ builder -> builder.setExp(Product::price, 1000) }
)
ArbitraryBuilder 타입 변환하기
map()
map()
함수는 ArbitraryBuilder 의 타입을 다른 타입으로 변환하는 데 사용됩니다.
fixtureMonkey.giveMeBuilder(Product.class)
.map(Product::getId); // ArbitraryBuilder<Long> 타입으로 변환
fixtureMonkey.giveMeBuilder(Product::class.java)
.map(Product::id) // ArbitraryBuilder<Long> 타입으로 변환
zipWith()
zipWith()
은 여러 ArbitraryBuilder를 병합하여 다른 타입의 ArbitraryBuilder를 만들 때 유용합니다.
빌더들을 어떻게 결합할 지 명시해야 합니다.
ArbitraryBuilder<String> stringBuilder = fixtureMonkey.giveMeBuilder(String.class);
ArbitraryBuilder<String> zipped = fixtureMonkey.giveMeBuilder(Integer.class)
.zipWith(stringBuilder, (integer, string) -> integer + "" + string);
val stringBuilder = fixtureMonkey.giveMeBuilder<String>()
val zipped = fixtureMonkey.giveMeBuilder<Int>()
.zipWith(stringBuilder) { int, string -> int.toString() + "" + string }