I'm working on a PoC where we use CQRS in combination with Event Sourcing. We use the Axon framework and Axon server as toolset.
We have some microservices (Maven packages) with some business logic.
A simple overview of the application flow:
We post a xml message (with REST) to service 1 that will result in an event (with Aggregate).
Service 2 handles the event "fired" by service 1 and starts a saga flow. Part of the sage flow is for example to send a mail message.
I can do some tests with Axon Test to test the aggregate from service 1 or the saga from service 2. But is there a good option to do a real integration test where we start with posting a message to the REST interface and check all the operations in the aggregate and saga (inclusive sending mail and so on)
Maybe this kind of integration test is overdone and it's better to test each component on it's own. I doubt what's needed / the best solution to test this type of system.
I suggest to have a look at testcontainers (https://www.testcontainers.org/)
It provides a very convenient way to start up and cleanly tear down docker containers in JUnit tests. This feature is very useful for integration testing of applications against real databases and any other resource (for example Axon Server) for which a docker image is available (https://hub.docker.com/r/axoniq/axonserver/).
I'm sharing some code snippets from JUnit 4 test class (Kotlin). Hopefully this can help you to get started and evolve your specific test strategy (integration should cover smaller scope then end-to-end tests). My opinion is that integration test should focus on Axon messaging API components and REST API components separately/independently. End-to-end should cover all components in your microservice/s.
#RunWith(SpringRunner::class)
#SpringBootTest
#ContextConfiguration(initializers = [DrestaurantCourierCommandMicroServiceIT.Initializer::class])
internal class DrestaurantCourierCommandMicroServiceIT {
#Autowired
lateinit var eventStore: EventStore
#Autowired
lateinit var commandGateway: CommandGateway
companion object {
// An Axon Server container
#ClassRule
#JvmField
var axonServerTestContainer = KGenericContainer(
"axoniq/axonserver")
.withExposedPorts(8024, 8124)
.waitingFor(Wait.forHttp("/actuator/info").forPort(8024))
.withStartupTimeout(Duration.of(60L, ChronoUnit.SECONDS))
// A PostgreSQL container is being started up using a JUnit Class Rule which gets triggered before any of the tests are run:
#ClassRule
#JvmField
var postgreSQLContainer = KPostgreSQLContainer(
"postgres:latest")
.withDatabaseName("drestaurant")
.withUsername("demouser")
.withPassword("thepassword")
.withStartupTimeout(Duration.of(60L, ChronoUnit.SECONDS))
}
// Pass details on the application as properties BEFORE Spring starts creating a test context for the test to run in:
class Initializer : ApplicationContextInitializer<ConfigurableApplicationContext> {
override fun initialize(configurableApplicationContext: ConfigurableApplicationContext) {
val values = TestPropertyValues.of(
"spring.datasource.url=" + postgreSQLContainer.jdbcUrl,
"spring.datasource.username=" + postgreSQLContainer.username,
"spring.datasource.password=" + postgreSQLContainer.password,
"spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect",
"axon.axonserver.servers=" + axonServerTestContainer.containerIpAddress + ":" + axonServerTestContainer.getMappedPort(8124)
)
values.applyTo(configurableApplicationContext)
}
}
#Test
fun `restaurant command microservice integration test - happy scenario`() {
val who = "johndoe"
val auditEntry = AuditEntry(who, Calendar.getInstance().time)
val maxNumberOfActiveOrders = 5
val name = PersonName("Ivan", "Dugalic")
val orderId = CourierOrderId("orderId")
// ******* Sending the `createCourierCommand` ***********
val createCourierCommand = CreateCourierCommand(name, maxNumberOfActiveOrders, auditEntry)
commandGateway.sendAndWait<Any>(createCourierCommand)
await withPollInterval org.awaitility.Duration.ONE_SECOND atMost org.awaitility.Duration.FIVE_SECONDS untilAsserted {
val latestCourierCreatedEvent = eventStore.readEvents(createCourierCommand.targetAggregateIdentifier.identifier).asStream().toList().last().payload as CourierCreatedEvent
assertThat(latestCourierCreatedEvent.name).isEqualTo(createCourierCommand.name)
assertThat(latestCourierCreatedEvent.auditEntry.who).isEqualTo(createCourierCommand.auditEntry.who)
assertThat(latestCourierCreatedEvent.maxNumberOfActiveOrders).isEqualTo(createCourierCommand.maxNumberOfActiveOrders)
}
// ******* Sending the `createCourierOrderCommand` **********
val createCourierOrderCommand = CreateCourierOrderCommand(orderId, auditEntry)
commandGateway.sendAndWait<Any>(createCourierOrderCommand)
await withPollInterval org.awaitility.Duration.ONE_SECOND atMost org.awaitility.Duration.FIVE_SECONDS untilAsserted {
val latestCourierOrderCreatedEvent = eventStore.readEvents(createCourierOrderCommand.targetAggregateIdentifier.identifier).asStream().toList().last().payload as CourierOrderCreatedEvent
assertThat(latestCourierOrderCreatedEvent.aggregateIdentifier.identifier).isEqualTo(createCourierOrderCommand.targetAggregateIdentifier.identifier)
assertThat(latestCourierOrderCreatedEvent.auditEntry.who).isEqualTo(createCourierOrderCommand.auditEntry.who)
}
// ******* Assign the courier order to courier **********
val assignCourierOrderToCourierCommand = AssignCourierOrderToCourierCommand(orderId, createCourierCommand.targetAggregateIdentifier, auditEntry)
commandGateway.sendAndWait<Any>(assignCourierOrderToCourierCommand)
await withPollInterval org.awaitility.Duration.ONE_SECOND atMost org.awaitility.Duration.FIVE_SECONDS untilAsserted {
val latestCourierOrderAssignedEvent = eventStore.readEvents(assignCourierOrderToCourierCommand.targetAggregateIdentifier.identifier).asStream().toList().last().payload as CourierOrderAssignedEvent
assertThat(latestCourierOrderAssignedEvent.aggregateIdentifier.identifier).isEqualTo(assignCourierOrderToCourierCommand.targetAggregateIdentifier.identifier)
assertThat(latestCourierOrderAssignedEvent.auditEntry.who).isEqualTo(assignCourierOrderToCourierCommand.auditEntry.who)
assertThat(latestCourierOrderAssignedEvent.courierId.identifier).isEqualTo(assignCourierOrderToCourierCommand.courierId.identifier)
}
}
}
class KGenericContainer(imageName: String) : GenericContainer<KGenericContainer>(imageName)
class KPostgreSQLContainer(imageName: String) : PostgreSQLContainer<KPostgreSQLContainer>(imageName)
Axon developers suggest to go with docker solution as mentioned here.
Testcontainers seems to be the best here.
My java snippet:
#ActiveProfiles("test")
public class TestContainers {
private static final int AXON_HTTP_PORT = 8024;
private static final int AXON_GRPC_PORT = 8124;
public static void startAxonServer() {
GenericContainer axonServer = new GenericContainer("axoniq/axonserver:latest")
.withExposedPorts(AXON_HTTP_PORT, AXON_GRPC_PORT)
.waitingFor(
Wait.forLogMessage(".*Started AxonServer.*", 1)
);
axonServer.start();
System.setProperty("ENV_AXON_GRPC_PORT", String.valueOf(axonServer.getMappedPort(AXON_GRPC_PORT)));
}
Call startAxonServer method in your #BeforeClass. Now you have to obtain external docker ports (these indicated in withExposedPorts are docker-internal).
You can do it in runtime via getMappedPort as shown in my snippet. Remember to provide connection configuration to your test suite. My example on spring boot as follows:
axon:
axonserver:
servers: localhost:${ENV_AXON_GRPC_PORT}
Complete working solution can be found on my github project.
Related
I am working on a Camunda java code and i am looking for a testing methodology that i can use to test any of my bpmn processes.
i have made some google search and i found on Camunda documentation some ideas about unit testing but it is do test for a specific bpmn model .
i need one to test any bpmn model (just by passing name of bpmn file and id of process etc)
the strategy should take into account the integration with DB to get candidate (user&group) for any expected path.i know maybe i can't do that but i have a large model and it will be time waste to test all of it in traditional ways.
Mohammad, your question is interesting - this goal can be achieved (test on bpmn can be dynamic), if we talk about not very detailed test.
Look at my code below, written by your idea of such common test (as i understood it, of course)
I use camunda-bpm-assert-scenario and camunda-bpm-assert libs in it.
First of all you gives to your test info about "wait states" in testing process (this can be done by json file - for not to change the code)
// optional - for mocking services for http-connector call, et.c.
private Map<String, Object> configs = withVariables(
"URL_TO_SERVICE", "http://mock-url.com/service"
);
private Map<String, Map<String, Object>> userTaskIdToResultVars = new LinkedHashMap<String, Map<String, Object>>() {{
put("user_task_0", withVariables(
"a", 0
));
put("user_task_1", withVariables(
"var0", "var0Value",
"var1", "var1Value"));
}};
// optional - if you want to check vars during process execution
private Map<String, Map<String, Object>> userTaskIdToAssertVars = new LinkedHashMap<String, Map<String, Object>>() {{
put("user_task_1", withVariables(
"a", 0
));
}};
Then you mocking user tasks (and other wait states) with given info:
#Mock
private ProcessScenario processScenario;
#Before
public void defineHappyScenario() {
MockitoAnnotations.initMocks(this);
for (String taskId : userTaskIdToResultVars.keySet()) {
when(processScenario.waitsAtUserTask(taskId)).thenReturn(
(task) -> {
// optional - if you want to check vars during process execution
Map<String, Object> varsToCheck = userTaskIdToAssertVars.get(taskId);
if (varsToCheck != null) {
assertThat(task.getProcessInstance())
.variables().containsAllEntriesOf(varsToCheck);
}
task.complete(userTaskIdToResultVars.get(taskId));
});
}
// If it needs, we describe mocks for other wait states in same way,
// when(processScenario.waitsAtSignalIntermediateCatchEvent(signalCatchId).thenReturn(...);
}
And your test will be anything like this:
#Test
#Deployment(resources = "diagram_2.bpmn")
public void testHappyPath() {
Scenario scenario = Scenario.run(processScenario).startByKey(PROCESS_DEFINITION_KEY, configs).execute();
ProcessInstance process = scenario.instance(processScenario);
assertThat(process).isStarted();
assertThat(process).hasPassedInOrder( // or check execution order of all elements -- not only wait-states (give them in additional file)
userTaskIdToResultVars.keySet().toArray(new String[0]) // user_task_0, user_task_1
);
assertThat(process).isEnded();
}
Hope this helps in your work.
In the Asp.Net core project, there are several unit tests used services for connecting to the database and bring real data, so multiple concurrent connections are created. When these tests run, I received this error
A second operation started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe.
but I do not know how can I fix this error without using async ways.
In unit tests you should not use connection to a DB. You should use mockups and create your own data to test with.
Use the NuGet package moqto easily create mockup objects.
Example of using the mockup objects:
public void Test_Login()
{
Mock<IDatabase> mockDatabase = new Mock<IDatabase>();
mockDatabase.Setup(p => p.GetAccountAsync(It.IsAny<string>()))
.Returns((string givenEmail) => Task.FromResult(new Account(1, "test", givenEmail, "123", "$2b$10$pfsnDQ3IWuY/zER/uBQpedvRFntMNHGOGhOSpABKZ7bwS", false)));
Mock<IConfiguration> mockConfiguration = new Mock<IConfiguration>();
Mock<IHostingEnvironment> mockHostingEnvironment = new Mock<IHostingEnvironment>();
AccountService accountService = new AccountService(mockDatabase.Object, mockConfiguration.Object, mockHostingEnvironment.Object);
LoginViewModel loginViewModel = new LoginViewModel
{
EmailLogin = "test#test.com",
PasswordLogin = "s"
};
Task<Account> account = accountService.LoginAsync(loginViewModel);
Assert.NotNull(account.Result);
Assert.Equal(loginViewModel.EmailLogin, account.Result.Email);
}
In the example above I manually set the value of the mockup database that the service method will use to retrieve the account and compare the returned email with the given email.
In my application, I need to call an external endpoint and if it is too slow a fallback is activated.
The following code is an example of how my app looks like:
#FeignClient(name = "${config.name}", url = "${config.url:}", fallback = ExampleFallback.class)
public interface Example {
#RequestMapping(method = RequestMethod.GET, value = "/endpoint", produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
MyReturnObject find(#RequestParam("myParam") String myParam);
}
And its fallback implementation:
#Component
public Class ExampleFallback implements Example {
private final FallbackService fallback;
#Autowired
public ExampleFallback(final FallbackService fallback) {
this.fallback = fallback;
}
#Override
public MyReturnObject find(final String myParam) {
return fallback.find(myParam);
}
Also, a configured timeout for circuit breaker:
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 5000
How can I implement an integration test to check if my circuit break is working, i.e, if my endpoint (mocked in that case) is slow or if it returns an error like 4xx or 5xx?
I'm using Spring Boot 1.5.3 with Spring Cloud (Feign + Hystrix)
Note i donot know Feign or Hystrix.
In my opinion it is problematic to implement an automated integrationtest that simulates different implementatondetails of Feign+Hystrix - this implementation detail can change at any time. There are many different types of failure: primary-Endpoint not reachable, illegal data (i.e. receiving a html-errormessage, when exprecting xml data in a special format), disk-full, .....
if you mock an endpoint you make an assumption of implementationdetail of Feign+Hystrix how the endpoint behaves in a errorsituation (i.e. return null, return some specific errorcode, throw an exception of type Xyz....)
i would create only one automated integration test with a real primary-enpoint that has a never reachable url and a mocked-fallback-endpoint where you verify that the processed data comes from the mock.
This automated test assumes that handling of "networkconnection too slow" is the same as "url-notfound" from your app-s point of view.
For all other tests i would create a thin wrapper interface around Feign+Hystrix where you mock Feign+Hystrix. This way you can automatically test for example what happens if you receive 200bytes from primary interface and then get an expetion.
For details about hiding external dependencies see onion-architecture
I'm using spring boot as it removes all the boring stuff and let's me focus on my code, but all the test examples use junit and I want to use cucumber?
Can someone point me in the right direction to get cucumber and spring to start things up, do all the auto config and wiring and let my step definitions use auto wired beans to do stuff?
Try to use the following on your step definition class:
#ContextConfiguration(classes = YourBootApplication.class,
loader = SpringApplicationContextLoader.class)
#RunWith(SpringJUnit4ClassRunner.class)
public class MySteps {
//...
}
Also make sure you have the cucumber-spring module on your classpath.
Jake - my final code had the following annotations in a superclass that each cucumber step definition class extended, This gives access to web based mocks, adds in various scopes for testing, and bootstraps Spring boot only once.
#ContextConfiguration(classes = {MySpringConfiguration.class}, loader = SpringApplicationContextLoader.class)
#WebAppConfiguration
#TestExecutionListeners({WebContextTestExecutionListener.class,ServletTestExecutionListener.class})
where WebContextTestExecutionListener is:
public class WebContextTestExecutionListener extends
AbstractTestExecutionListener {
#Override
public void prepareTestInstance(TestContext testContext) throws Exception {
if (testContext.getApplicationContext() instanceof GenericApplicationContext) {
GenericApplicationContext context = (GenericApplicationContext) testContext.getApplicationContext();
ConfigurableListableBeanFactory beanFactory = context
.getBeanFactory();
Scope requestScope = new RequestScope();
beanFactory.registerScope("request", requestScope);
Scope sessionScope = new SessionScope();
beanFactory.registerScope("session", sessionScope);
}
}
}
My approach is quite simple. In a Before hook (in env.groovy as I am using Cucumber-JVM for Groovy), do the following.
package com.example.hooks
import static cucumber.api.groovy.Hooks.Before
import static org.springframework.boot.SpringApplication.exit
import static org.springframework.boot.SpringApplication.run
def context
Before {
if (!context) {
context = run Application
context.addShutdownHook {
exit context
}
}
}
Thanks to #PaulNUK, I found a set of annotations that will work.
I posted the answer in my question here
My StepDefs class required the annotations:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = DemoApplication.class, loader = SpringApplicationContextLoader.class)
#WebAppConfiguration
#IntegrationTest
There is also a repository with source code in answer I linked.
I just started a new job and one of the first things I've been asked to do is build unit tests for the code base (the company I now work for is committed to automated testing but they do mostly integration tests and the build takes forever to complete).
So everything started nicely, I started to break dependencies here and there and started writing isolated unit tests but now I'm having an issue with rhino mocks not being able to handle the following situation:
//authenticationSessionManager is injected through the constructor.
var authSession = authenticationSessionManager.GetSession(new Guid(authentication.SessionId));
((IExpirableSessionContext)authSession).InvalidateEnabled = false;
The type that the GetSession method returns is SessionContext and as you can see it gets casted into the IExpirableSessionContext interface.
There is also an ExpirableSessionContext object that inherits from SessionContext and implements the IExpirableSessionContext interface.
The way the session object is stored and retrieved is shown in the following snippet:
private readonly Dictionary<Guid, SessionContext<TContent>> Sessions= new Dictionary<Guid, SessionContext<TContent>>();
public override SessionContext<TContent> GetSession(Guid sessionId)
{
var session = base.GetSession(sessionId);
if (session != null)
{
((IExpirableSessionContext)session).ResetTimeout();
}
return session;
}
public override SessionContext<TContent> CreateSession(TContent content)
{
var session = new ExpirableSessionContext<TContent>(content, SessionTimeoutMilliseconds, new TimerCallback(InvalidateSession));
Sessions.Add(session.Id, session);
return session;
}
Now my problem is when I mock the call to GetSession, even though I'm telling rhino mocks to return an ExpirableSessionContext<...> object, the test throws an exception on the line where it's being casted into the IExpirableSession interface, here is the code in my test (I know I'm using the old syntax, please bear with me on this one):
Mocks = new MockRepository();
IAuthenticationSessionManager AuthenticationSessionMock;
AuthenticationSessionMock = Mocks.DynamicMock<IAuthenticationSessionManager>();
var stationAgentManager = new StationAgentManager(AuthenticationSessionMock);
var authenticationSession = new ExpirableSessionContext<AuthenticationSessionContent>(new AuthenticationSessionContent(AnyUserName, AnyPassword), 1, null);
using (Mocks.Record())
{
Expect.Call(AuthenticationSessionMock.GetSession(Guid.NewGuid())).IgnoreArguments().Return(authenticationSession);
}
using (Mocks.Playback())
{
var result = stationAgentManager.StartDeploymentSession(anyAuthenticationCookie);
Assert.IsFalse(((IExpirableSessionContext)authenticationSession).InvalidateEnabled);
}
I think it makes sense the cast fails since the method returns a different kind of object and the production code works since the session is being created as the correct type and stored in a dictionary which is code the test will never run since it is being mocked.
How can I set this test up to run correctly?
Thank you for any help you can provide.
Turns out everything is working fine, the problem was that on the setup for each test there is an expectation on that method call:
Expect.Call(AuthenticationSessionMock.GetSession(anySession.Id)).Return(anySession).Repeat.Any();
So this expectation was overriding the one I set on my own test. I had to take this expectation out of the setup method, include it on a helper method and have all the other tests use this one instead.
Once out of the way, my test started working.