Skip to content

Commit

Permalink
Test fixtures now fail when adding an aggregate that already exists
Browse files Browse the repository at this point in the history
Due to an issue, test fixtures would not report this failure, but
silently accept these events.

Issue #AXON-282 Fixed
  • Loading branch information
abuijze committed Dec 12, 2014
1 parent 5d0dfa2 commit a186531
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 95 deletions.
Expand Up @@ -55,7 +55,6 @@
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.HashSet;
Expand All @@ -81,21 +80,18 @@ public class GivenWhenThenTestFixture<T extends EventSourcedAggregateRoot>
implements FixtureConfiguration<T>, TestExecutor {

private static final Logger logger = LoggerFactory.getLogger(GivenWhenThenTestFixture.class);

private final Class<T> aggregateType;
private Repository<T> repository;
private SimpleCommandBus commandBus;
private EventBus eventBus;
private Object aggregateIdentifier;

private EventStore eventStore;

private Collection<DomainEventMessage> givenEvents;
private Deque<DomainEventMessage> givenEvents;
private Deque<DomainEventMessage> storedEvents;
private List<EventMessage> publishedEvents;
private long sequenceNumber = 0;
private AggregateRoot workingAggregate;
private boolean reportIllegalStateChange = true;
private final Class<T> aggregateType;
private boolean explicitCommandHandlersSet;

/**
Expand Down Expand Up @@ -342,7 +338,7 @@ private void ensureValuesEqual(Object workingValue, Object eventSourcedValue, St
private void clearGivenWhenState() {
storedEvents = new LinkedList<DomainEventMessage>();
publishedEvents = new ArrayList<EventMessage>();
givenEvents = new ArrayList<DomainEventMessage>();
givenEvents = new LinkedList<DomainEventMessage>();
sequenceNumber = 0;
}

Expand Down Expand Up @@ -372,15 +368,100 @@ public Repository<T> getRepository() {
return repository;
}

private static class ComparationEntry {

private final Object workingObject;
private final Object eventSourceObject;

public ComparationEntry(Object workingObject, Object eventSourceObject) {
this.workingObject = workingObject;
this.eventSourceObject = eventSourceObject;
}

@SuppressWarnings("RedundantIfStatement")
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}

ComparationEntry that = (ComparationEntry) o;

if (!eventSourceObject.equals(that.eventSourceObject)) {
return false;
}
if (!workingObject.equals(that.workingObject)) {
return false;
}

return true;
}

@Override
public int hashCode() {
int result = workingObject.hashCode();
result = 31 * result + eventSourceObject.hashCode();
return result;
}
}

private static class IdentifierValidatingRepository<T extends AggregateRoot> implements Repository<T> {

private final Repository<T> delegate;

public IdentifierValidatingRepository(Repository<T> delegate) {
this.delegate = delegate;
}

@Override
public T load(Object aggregateIdentifier, Long expectedVersion) {
T aggregate = delegate.load(aggregateIdentifier, expectedVersion);
validateIdentifier(aggregateIdentifier, aggregate);
return aggregate;
}

@Override
public T load(Object aggregateIdentifier) {
T aggregate = delegate.load(aggregateIdentifier, null);
validateIdentifier(aggregateIdentifier, aggregate);
return aggregate;
}

private void validateIdentifier(Object aggregateIdentifier, T aggregate) {
if (aggregateIdentifier != null && !aggregateIdentifier.equals(aggregate.getIdentifier())) {
throw new AssertionError(String.format(
"The aggregate used in this fixture was initialized with an identifier different than "
+ "the one used to load it. Loaded [%s], but actual identifier is [%s].\n"
+ "Make sure the identifier passed in the Command matches that of the given Events.",
aggregateIdentifier, aggregate.getIdentifier()));
}
}

@Override
public void add(T aggregate) {
delegate.add(aggregate);
}
}

private class RecordingEventStore implements EventStore {

@Override
public void appendEvents(String type, DomainEventStream events) {
while (events.hasNext()) {
DomainEventMessage next = events.next();
validateIdentifier(next.getAggregateIdentifier().getClass());
if (!storedEvents.isEmpty()) {
DomainEventMessage lastEvent = storedEvents.peekLast();

if (aggregateIdentifier == null) {
aggregateIdentifier = next.getAggregateIdentifier();
injectAggregateIdentifier();
}

DomainEventMessage lastEvent = (storedEvents.isEmpty() ? givenEvents : storedEvents).peekLast();

if (lastEvent != null) {
if (!lastEvent.getAggregateIdentifier().equals(next.getAggregateIdentifier())) {
throw new EventStoreException("Writing events for an unexpected aggregate. This could "
+ "indicate that a wrong aggregate is being triggered.");
Expand All @@ -391,10 +472,6 @@ public void appendEvents(String type, DomainEventStream events) {
next.getSequenceNumber()));
}
}
if (aggregateIdentifier == null) {
aggregateIdentifier = next.getAggregateIdentifier();
injectAggregateIdentifier();
}
storedEvents.add(next);
}
}
Expand Down Expand Up @@ -475,84 +552,6 @@ public void onPrepareCommit(UnitOfWork unitOfWork, Set<AggregateRoot> aggregateR
}
}

private static class ComparationEntry {

private final Object workingObject;
private final Object eventSourceObject;

public ComparationEntry(Object workingObject, Object eventSourceObject) {
this.workingObject = workingObject;
this.eventSourceObject = eventSourceObject;
}

@SuppressWarnings("RedundantIfStatement")
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}

ComparationEntry that = (ComparationEntry) o;

if (!eventSourceObject.equals(that.eventSourceObject)) {
return false;
}
if (!workingObject.equals(that.workingObject)) {
return false;
}

return true;
}

@Override
public int hashCode() {
int result = workingObject.hashCode();
result = 31 * result + eventSourceObject.hashCode();
return result;
}
}

private static class IdentifierValidatingRepository<T extends AggregateRoot> implements Repository<T> {

private final Repository<T> delegate;

public IdentifierValidatingRepository(Repository<T> delegate) {
this.delegate = delegate;
}

@Override
public T load(Object aggregateIdentifier, Long expectedVersion) {
T aggregate = delegate.load(aggregateIdentifier, expectedVersion);
validateIdentifier(aggregateIdentifier, aggregate);
return aggregate;
}

@Override
public T load(Object aggregateIdentifier) {
T aggregate = delegate.load(aggregateIdentifier, null);
validateIdentifier(aggregateIdentifier, aggregate);
return aggregate;
}

private void validateIdentifier(Object aggregateIdentifier, T aggregate) {
if (aggregateIdentifier != null && !aggregateIdentifier.equals(aggregate.getIdentifier())) {
throw new AssertionError(String.format(
"The aggregate used in this fixture was initialized with an identifier different than "
+ "the one used to load it. Loaded [%s], but actual identifier is [%s].\n"
+ "Make sure the identifier passed in the Command matches that of the given Events.",
aggregateIdentifier, aggregate.getIdentifier()));
}
}

@Override
public void add(T aggregate) {
delegate.add(aggregate);
}
}

private class ExecutionExceptionAwareCallback implements CommandCallback<Object> {

private FixtureExecutionException exception;
Expand Down
Expand Up @@ -106,8 +106,8 @@ public void testFixtureGivenCommands_ResourcesAvailable() {
public void testAggregate_InjectCustomResourceAfterCreatingAnnotatedHandler() {
// a 'when' will cause command handlers to be registered.
fixture.registerInjectableResource(new HardToCreateResource());
fixture.given(new MyEvent("AggregateId", 1), new MyEvent("AggregateId", 2)).
when(new CreateAggregateCommand());
fixture.given()
.when(new CreateAggregateCommand("AggregateId"));
fixture.registerInjectableResource("I am injectable");
}

Expand Down
13 changes: 11 additions & 2 deletions test/src/test/java/org/axonframework/test/FixtureTest_Generic.java
Expand Up @@ -58,7 +58,7 @@ public void testConfigureCustomAggregateFactory() {
fixture.registerAnnotatedCommandHandler(new MyCommandHandler(fixture.getRepository(), fixture.getEventBus()));

fixture.given(new MyEvent("id1", 1))
.when(new TestCommand("id1"));
.when(new TestCommand("id1"));

verify(mockAggregateFactory).createAggregate(eq("id1"), isA(DomainEventMessage.class));
}
Expand All @@ -83,6 +83,15 @@ public void testAggregateIdentifier_ServerGeneratedIdentifier() {
.when(new CreateAggregateCommand());
}

@Test
public void testStoringExistingAggregateGeneratesException() {
fixture.registerAggregateFactory(mockAggregateFactory);
fixture.registerAnnotatedCommandHandler(new MyCommandHandler(fixture.getRepository(), fixture.getEventBus()));
fixture.given(new MyEvent("aggregateId", 1))
.when(new CreateAggregateCommand("aggregateId"))
.expectException(EventStoreException.class);
}

@Test(expected = FixtureExecutionException.class)
public void testInjectResources_CommandHandlerAlreadyRegistered() {
fixture.registerAggregateFactory(mockAggregateFactory);
Expand All @@ -99,7 +108,7 @@ public void testAggregateIdentifier_IdentifierAutomaticallyDeducted() {
.expectEvents(new MyEvent("AggregateId", 3));

DomainEventStream events = fixture.getEventStore().readEvents("StandardAggregate", "AggregateId");
for (int t=0;t<3;t++) {
for (int t = 0; t < 3; t++) {
assertTrue(events.hasNext());
DomainEventMessage next = events.next();
assertEquals("AggregateId", next.getAggregateIdentifier());
Expand Down

0 comments on commit a186531

Please sign in to comment.