GH-1 - Update Maven artifact coordinates, package and directory structure.

Original pull request: GH-7.
This commit is contained in:
Björn Kieling
2022-07-07 12:16:07 +02:00
committed by Oliver Drotbohm
parent 6c444769d7
commit 417e215993
255 changed files with 764 additions and 759 deletions

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.acme.withatbean;
import javax.sql.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author Oliver Drotbohm
*/
@Configuration
public class SampleConfiguration {
@Bean
DataSource dataSource() {
return null;
}
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright 2020-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.acme.withatbean;
/**
* @author Oliver Drotbohm
*/
public class TestEvents {
/**
* Method calling a factory method.
*/
public void method() {
JMoleculesAnnotated.of();
}
/**
* Method calling a constructor.
*/
public void constructorCall() {
new JMoleculesAnnotated();
}
// jMolecules
@org.jmolecules.event.annotation.DomainEvent
public static class JMoleculesAnnotated {
public static JMoleculesAnnotated of() {
return null;
}
}
public static class JMoleculesImplementing implements org.jmolecules.event.types.DomainEvent {}
}

View File

@@ -0,0 +1,2 @@
@org.jmolecules.ddd.annotation.Module
package jmolecules;

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.modulith.model;
import static org.assertj.core.api.Assertions.*;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import org.junit.jupiter.api.Test;
import org.springframework.modulith.Modulithic;
/**
* Unit tests for {@link AnnotationModulithMetadata}.
*
* @author Oliver Drotbohm
*/
class AnnotationModulithMetadataUnitTest {
@Test
void findsCustomizationsOnClass() {
assertThat(AnnotationModulithMetadata.of(Sample.class)).hasValueSatisfying(it -> {
assertThat(it.useFullyQualifiedModuleNames()).isTrue();
});
}
@Test
void findsCustomizationsOnClassForMetaAnnotationUsage() {
assertThat(AnnotationModulithMetadata.of(MetaSample.class)).hasValueSatisfying(it -> {
assertThat(it.useFullyQualifiedModuleNames()).isTrue();
});
}
@Modulithic(useFullyQualifiedModuleNames = true)
static class Sample {}
@Intermediate
static class MetaSample {}
@Retention(RetentionPolicy.RUNTIME)
@Modulithic(useFullyQualifiedModuleNames = true)
@interface Intermediate {
}
}

View File

@@ -0,0 +1,258 @@
/*
* Copyright 2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.modulith.model;
import static org.assertj.core.api.Assertions.*;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Stream;
import javax.persistence.Entity;
import org.jmolecules.event.annotation.DomainEventHandler;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestFactory;
import org.springframework.modulith.model.ArchitecturallyEvidentType.SpringAwareArchitecturallyEvidentType;
import org.springframework.modulith.model.ArchitecturallyEvidentType.SpringDataAwareArchitecturallyEvidentType;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.EventListener;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import com.tngtech.archunit.core.domain.JavaClass;
/**
* Unit tests for {@link ArchitecturallyEvidentType}.
*
* @author Oliver Drotbohm
*/
class ArchitecturallyEvidentTypeUnitTest {
Classes classes = TestUtils.getClasses();
JavaClass self = classes.getRequiredClass(ArchitecturallyEvidentTypeUnitTest.class);
@Test
void abbreviatesFullyQualifiedTypeName() {
ArchitecturallyEvidentType type = ArchitecturallyEvidentType.of(self, classes);
assertThat(type.getAbbreviatedFullName()).isEqualTo("o.s.m.m.ArchitecturallyEvidentTypeUnitTest");
}
@Test
void doesNotConsiderArbitraryTypeAStereotype() {
ArchitecturallyEvidentType type = ArchitecturallyEvidentType.of(self, classes);
assertThat(type.isEntity()).isFalse();
assertThat(type.isAggregateRoot()).isFalse();
assertThat(type.isRepository()).isFalse();
}
@Test
void detectsSpringAnnotatedRepositories() {
ArchitecturallyEvidentType type = new SpringAwareArchitecturallyEvidentType(
classes.getRequiredClass(SpringRepository.class));
assertThat(type.isRepository()).isTrue();
}
@Test
void doesNotConsiderEntityAggregateRoot() {
ArchitecturallyEvidentType type = new SpringAwareArchitecturallyEvidentType(
classes.getRequiredClass(SampleEntity.class));
assertThat(type.isEntity()).isTrue();
assertThat(type.isAggregateRoot()).isFalse();
}
@Test
void considersEntityAnAggregateRootIfTheresARepositoryForIt() {
Map<Class<?>, Boolean> parameters = new HashMap<Class<?>, Boolean>();
parameters.put(SampleEntity.class, true);
parameters.put(OtherEntity.class, false);
parameters.put(NoEntity.class, false);
parameters.entrySet().stream().forEach(it -> {
JavaClass entity = classes.getRequiredClass(it.getKey());
assertThat(new SpringDataAwareArchitecturallyEvidentType(entity, classes).isAggregateRoot())
.isEqualTo(it.getValue());
});
}
@TestFactory
Stream<DynamicTest> considersJMoleculesEntity() {
return DynamicTest.stream(getTypesFor(JMoleculesAnnotatedEntity.class, JMoleculesImplementingEntity.class), //
it -> String.format("%s is considered an entity", it.getType().getSimpleName()), //
it -> {
assertThat(it.isEntity()).isTrue();
assertThat(it.isAggregateRoot()).isFalse();
assertThat(it.isRepository()).isFalse();
});
}
@TestFactory
Stream<DynamicTest> considersJMoleculesAggregateRoot() {
return DynamicTest.stream(
getTypesFor(JMoleculesAnnotatedAggregateRoot.class, JMoleculesImplementingAggregateRoot.class), //
it -> String.format("%s is considered an entity, aggregate root but not a repository",
it.getType().getSimpleName()), //
it -> {
assertThat(it.isEntity()).isTrue();
assertThat(it.isAggregateRoot()).isTrue();
assertThat(it.isRepository()).isFalse();
});
}
@TestFactory
Stream<DynamicTest> considersJMoleculesRepository() {
return DynamicTest.stream(getTypesFor(JMoleculesAnnotatedRepository.class), //
it -> String.format("%s is considered a repository", it.getType().getSimpleName()), //
it -> {
assertThat(it.isEntity()).isFalse();
assertThat(it.isAggregateRoot()).isFalse();
assertThat(it.isRepository()).isTrue();
});
}
@Test
void discoversEventsListenedToForEventListener() {
JavaClass listenerType = classes.getRequiredClass(SomeEventListener.class);
assertThat(ArchitecturallyEvidentType.of(listenerType, classes).getReferenceTypes()) //
.extracting(JavaClass::getFullName) //
.containsExactly(Object.class.getName(), String.class.getName());
}
@Test
void discoversImplementingEventListener() {
JavaClass listenerType = classes.getRequiredClass(ImplementingEventListener.class);
assertThat(ArchitecturallyEvidentType.of(listenerType, classes).getReferenceTypes()) //
.extracting(JavaClass::getFullName) //
.containsExactly(ApplicationReadyEvent.class.getName());
}
@Test
void discoversJMoleculesEventHandler() {
JavaClass type = classes.getRequiredClass(JMoleculesEventListener.class);
assertThat(ArchitecturallyEvidentType.of(type, classes).isEventListener()).isTrue();
}
@Test
void discoversJMoleculesRepository() {
JavaClass type = classes.getRequiredClass(JMoleculesImplementingRepository.class);
assertThat(ArchitecturallyEvidentType.of(type, classes).isRepository()).isTrue();
}
private Iterator<ArchitecturallyEvidentType> getTypesFor(Class<?>... types) {
return Stream.of(types) //
.map(classes::getRequiredClass) //
.map(it -> ArchitecturallyEvidentType.of(it, classes)) //
.iterator();
}
// Spring
@Repository
interface SpringRepository {}
@Entity
class SampleEntity {}
// Spring Data
interface SampleRepository extends CrudRepository<SampleEntity, UUID> {}
@Entity
class OtherEntity {}
class NoEntity {}
// jMolecules
@org.jmolecules.ddd.annotation.Entity
class JMoleculesAnnotatedEntity {}
@org.jmolecules.ddd.annotation.AggregateRoot
class JMoleculesAnnotatedAggregateRoot {}
class JMoleculesImplementingIdentifier implements org.jmolecules.ddd.types.Identifier {}
abstract class JMoleculesImplementingEntity
implements
org.jmolecules.ddd.types.Entity<JMoleculesImplementingAggregateRoot, JMoleculesImplementingIdentifier> {}
abstract class JMoleculesImplementingAggregateRoot
implements
org.jmolecules.ddd.types.AggregateRoot<JMoleculesImplementingAggregateRoot, JMoleculesImplementingIdentifier> {}
@org.jmolecules.ddd.annotation.Repository
class JMoleculesAnnotatedRepository {}
interface JMoleculesEventListener {
@DomainEventHandler
void on(Object event);
}
interface JMoleculesImplementingRepository extends
org.jmolecules.ddd.types.Repository<JMoleculesImplementingAggregateRoot, JMoleculesImplementingIdentifier> {}
// Spring
class SomeEventListener {
@EventListener
void on(Object event) {}
@EventListener
void on(String event) {}
@EventListener
void onOther(Object event) {}
}
class ImplementingEventListener implements ApplicationListener<ApplicationReadyEvent> {
/*
* (non-Javadoc)
* @see org.springframework.context.ApplicationListener#onApplicationEvent(org.springframework.context.ApplicationEvent)
*/
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {}
}
}

View File

@@ -0,0 +1,107 @@
/*
* Copyright 2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.modulith.model;
import static org.assertj.core.api.Assertions.*;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
import org.springframework.modulith.model.Module.ModuleDependency;
import org.springframework.beans.factory.annotation.Autowired;
import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.core.importer.ClassFileImporter;
/**
* Unit tests for {@link ModuleDependency}.
*
* @author Oliver Drotbohm
*/
class ModuleDependencyUnitTest {
ClassFileImporter importer = new ClassFileImporter();
@Test
public void detectsInjectionDependencies() {
assertThat(findDependencies(SubType.class)) //
.containsExactlyInAnyOrder(A.class, B.class, C.class, D.class, E.class, F.class);
}
@Test
public void detectsDependencyFromAnnotatedConstructor() {
assertThat(findDependencies(MultipleConstructors.class)) //
.containsExactlyInAnyOrder(B.class);
}
@Test
public void detectsDependencyFromSingleUnannotatedConstructor() {
assertThat(findDependencies(SingleConstructor.class)) //
.containsExactlyInAnyOrder(B.class);
}
private Stream<Class<?>> findDependencies(Class<?> type) {
return ModuleDependency.fromType(importer.importClass(type)) //
.map(ModuleDependency::getTarget) //
.map(JavaClass::reflect);
}
static class A {}
static class B {}
static class C {}
static class D {}
static class E {}
static class F {}
static class SomeComponent {
@Autowired A a;
@Autowired
void setD(D d) {}
}
static class SubType extends SomeComponent {
@Autowired E e;
SubType(B b, C c) {}
@Autowired
void setF(F f) {}
}
static class MultipleConstructors {
MultipleConstructors(A a) {}
@Autowired
MultipleConstructors(B b) {}
}
static class SingleConstructor {
SingleConstructor(B b) {}
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2020-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.modulith.model;
import static org.assertj.core.api.Assertions.*;
import org.junit.jupiter.api.Test;
import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.core.importer.ImportOption;
/**
* Unit tests for {@link ModuleDetectionStrategy}.
*
* @author Oliver Drotbohm
*/
class ModuleDetectionStrategyUnitTest {
@Test
void usesExplicitlyAnnotatedConstant() {
assertThat(ModuleDetectionStrategy.explictlyAnnotated())
.isEqualTo(ModuleDetectionStrategies.EXPLICITLY_ANNOTATED);
}
@Test
void usesDirectSubPackages() {
assertThat(ModuleDetectionStrategy.directSubPackage())
.isEqualTo(ModuleDetectionStrategies.DIRECT_SUB_PACKAGES);
}
@Test
void detectsJMoleculesAnnotatedModule() {
JavaClasses classes = new ClassFileImporter() //
.withImportOption(new ImportOption.OnlyIncludeTests()) //
.importPackages("jmolecules");
JavaPackage javaPackage = JavaPackage.of(Classes.of(classes), "jmolecules");
assertThat(ModuleDetectionStrategy.explictlyAnnotated().getModuleBasePackages(javaPackage))
.containsExactly(javaPackage);
}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright 2019-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.modulith.model;
import static org.assertj.core.api.Assertions.*;
import java.util.List;
import javax.sql.DataSource;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
import com.acme.withatbean.TestEvents.JMoleculesAnnotated;
import com.acme.withatbean.TestEvents.JMoleculesImplementing;
import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
/**
* Unit tests for {@link Module}.
*
* @author Oliver Drotbohm
*/
@TestInstance(Lifecycle.PER_CLASS)
class ModuleUnitTest {
ClassFileImporter importer = new ClassFileImporter();
JavaClasses classes = importer.importPackages("com.acme.withatbean"); //
JavaPackage javaPackage = JavaPackage.of(Classes.of(classes), "");
Module module = new Module(javaPackage, false);
@Test
public void considersExternalSpringBeans() {
assertThat(module.getSpringBeans()) //
.flatExtracting(SpringBean::getFullyQualifiedTypeName) //
.contains(DataSource.class.getName());
}
@Test
void discoversPublishedEvents() {
JavaClass jMoleculesAnnotated = classes.get(JMoleculesAnnotated.class);
JavaClass jMoleculesImplementing = classes.get(JMoleculesImplementing.class);
List<EventType> events = module.getPublishedEvents();
assertThat(events.stream().map(EventType::getType)) //
.containsExactlyInAnyOrder(jMoleculesAnnotated, jMoleculesImplementing);
assertThat(events.stream().filter(it -> it.getType().equals(jMoleculesAnnotated))) //
.element(0) //
.satisfies(it -> {
assertThat(it.getSources()).isNotEmpty();
});
}
}

View File

@@ -0,0 +1,85 @@
/*
* Copyright 2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.modulith.model;
import static org.assertj.core.api.Assertions.*;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
import org.springframework.modulith.Modulith;
import org.springframework.modulith.Modulithic;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* Unit tests for {@link ModulithMetadata}.
*
* @author Oliver Drotbohm
*/
class ModulithMetadataUnitTest {
@Test
public void inspectsModulithAnnotation() throws Exception {
Stream.of(ModulithAnnotated.class, ModuliticAnnotated.class) //
.map(ModulithMetadata::of) //
.forEach(it -> {
assertThat(it.getAdditionalPackages()).containsExactly("com.acme.foo");
assertThat(it.getSharedModuleNames()).containsExactly("shared.module");
assertThat(it.getSystemName()).hasValue("systemName");
assertThat(it.useFullyQualifiedModuleNames()).isTrue();
});
}
@Test
public void usesDefaultsIfModulithAnnotationsAreMissing() {
ModulithMetadata metadata = ModulithMetadata.of(SpringBootApplicationAnnotated.class);
assertThat(metadata.getAdditionalPackages()).isEmpty();
assertThat(metadata.getSharedModuleNames()).isEmpty();
assertThat(metadata.getSystemName()).isEmpty();
assertThat(metadata.useFullyQualifiedModuleNames()).isFalse();
}
@Test
public void rejectsTypeNotAnnotatedWithEitherModulithAnnotationOrSpringBootApplication() {
assertThatExceptionOfType(IllegalArgumentException.class) //
.isThrownBy(() -> ModulithMetadata.of(Unannotated.class)) //
.withMessageContaining(Modulith.class.getSimpleName()) //
.withMessageContaining(Modulithic.class.getSimpleName()) //
.withMessageContaining(SpringBootApplication.class.getSimpleName());
}
@Modulith(additionalPackages = "com.acme.foo", //
sharedModules = "shared.module", //
systemName = "systemName", //
useFullyQualifiedModuleNames = true)
static class ModulithAnnotated {}
@Modulithic(additionalPackages = "com.acme.foo", //
sharedModules = "shared.module", //
systemName = "systemName", //
useFullyQualifiedModuleNames = true)
static class ModuliticAnnotated {}
@SpringBootApplication
static class SpringBootApplicationAnnotated {}
static class Unannotated {}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.modulith.model;
import static com.tngtech.archunit.core.domain.JavaClass.Predicates.*;
import org.jmolecules.ddd.annotation.AggregateRoot;
import org.springframework.data.repository.Repository;
import org.springframework.util.Assert;
import com.tngtech.archunit.base.DescribedPredicate;
import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.thirdparty.com.google.common.base.Supplier;
import com.tngtech.archunit.thirdparty.com.google.common.base.Suppliers;
/**
* Utilities for testing.
*
* @author Oliver Drotbohm
*/
class TestUtils {
private static Supplier<JavaClasses> imported = Suppliers.memoize(() -> new ClassFileImporter() //
.importPackagesOf(Modules.class, Repository.class, AggregateRoot.class));
private static DescribedPredicate<JavaClass> IS_MODULE_TYPE = JavaClass.Predicates
.resideInAPackage(Modules.class.getPackage().getName());
private static Supplier<Classes> classes = Suppliers.memoize(() -> Classes.of(imported.get()).that(IS_MODULE_TYPE));
/**
* Returns all {@link Classes} of this module.
*
* @return
*/
public static Classes getClasses() {
return classes.get();
}
public static JavaClasses getJavaClasses() {
return imported.get().that(IS_MODULE_TYPE);
}
/**
* Returns all {@link Classes} in the package of the given type.
*
* @param packageType must not be {@literal null}.
* @return
*/
public static Classes getClasses(Class<?> packageType) {
Assert.notNull(packageType, "Package type must not be null!");
return getClasses().that(resideInAPackage(packageType.getPackage().getName()));
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright 2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.modulith.model;
import static org.assertj.core.api.Assertions.*;
import org.junit.jupiter.api.Test;
/**
* Unit tests for {@link Violations}.
*
* @author Oliver Drotbohm
*/
class ViolationsUnitTests {
@Test
void combinesExceptionMessages() {
Violations violations = Violations.NONE //
.and(new IllegalArgumentException("First")) //
.and(new IllegalArgumentException("Second"));
assertThat(violations.getMessage()) //
.isEqualTo("- First\n- Second");
}
}