Axon message receives but event handler not call.
I am trying to implement the event sourcing in both the side with tow different queue.
My First Queue is test and the Second one is testdemo
I have two separate application running on the same server.
User Management
Wallet Management
I have implemented the event sourcing from User Management to wallet management. and it is working fine.
Now I am trying to implement the wallet management to UserManagement, Means that When I will publish the event from the wallet management ( Producer )
and ( Consume ) the user management application. So the event is received but event handler is not called.
Following is my application code. Please help me to figure out what I will be missing.
My Axon Configuration Class
package com.peaas.ngapblueprintdemo.config;
import org.axonframework.amqp.eventhandling.DefaultAMQPMessageConverter;
import org.axonframework.amqp.eventhandling.spring.SpringAMQPMessageSource;
import org.axonframework.serialization.Serializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.AmqpAdmin;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Exchange;
import org.springframework.amqp.core.ExchangeBuilder;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.QueueBuilder;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.rabbitmq.client.Channel;
#Configuration
public class AxonConfiguration {
private final static Logger logger = LoggerFactory.getLogger(AxonConfiguration.class);
#Value("${axon.amqp.exchange}")
private String exchange;
#Bean
public Exchange exchange() {
logger.info(exchange + " AMQP Exchange Registering ");
return ExchangeBuilder.fanoutExchange(exchange).build();
}
#Bean
public Queue queue() {
return QueueBuilder.durable(exchange).build();
}
#Bean
public Binding binding() {
return BindingBuilder.bind(queue()).to(exchange()).with("*").noargs();
}
#Autowired
public void configure(AmqpAdmin amqpAdmin) {
amqpAdmin.declareExchange(exchange());
amqpAdmin.declareQueue(queue());
amqpAdmin.declareBinding(binding());
}
#Bean
public SpringAMQPMessageSource testdemo(Serializer serializer) {
System.out.println("--- On Message Call ---");
return new SpringAMQPMessageSource(new DefaultAMQPMessageConverter(serializer)) {
#RabbitListener(queues = "testdemo")
#Override
public void onMessage(Message message, Channel channel) throws Exception {
System.out.println(message.getMessageProperties());
System.out.println("channel == "+channel);
super.onMessage(message, channel);
}
};
}
}
WalletCreatedEvent Class
package com.peaas.ngapblueprintdemo.events;
public class WalletCreatedEvent {
private Long id;
private String walletId;
private Double amount;
private Long userId;
public WalletCreatedEvent(Long id, String walletId, Double amount, Long userId) {
super();
System.out.println("--- call ---");
this.id = id;
this.walletId = walletId;
this.amount = amount;
this.userId = userId;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Double getAmount() {
return amount;
}
public void setAmount(Double amount) {
this.amount = amount;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public String getWalletId() {
return walletId;
}
public void setWalletId(String walletId) {
this.walletId = walletId;
}
#Override
public String toString() {
return "WalletCreatedEvent [id=" + id + ", walletId=" + walletId + ", amount=" + amount + ", userId=" + userId
+ "]";
}
}
EventHandler Class
package com.peaas.ngapblueprintdemo.eventHandlers;
import org.axonframework.eventhandling.EventHandler;
import org.springframework.stereotype.Component;
import com.peaas.ngapblueprintdemo.events.WalletCreatedEvent;
#Component
public class UserEventHandler {
#EventHandler
public void onCreateWalletEvent(WalletCreatedEvent event) {
System.out.println("--- Wallet Created Successfully ---");
System.out.println(event);
}
}
Following is my application.yml file properties
axon:
amqp:
exchange: test
eventhandling:
processors:
amqpEvents:
source: testdemo
Following is my log data that showing event is received.
MessageProperties [headers={axon-message-id=fa60968c-6905-46b5-8afe-6da853a4c51a, axon-message-aggregate-seq=0, axon-metadata-correlationId=589ef284-176f-49b8-aae0-0ad1588fa735, axon-message-aggregate-type=WalletAggregate, axon-message-revision=null, axon-message-timestamp=2018-08-06T11:09:26.345Z, axon-message-type=com.peaas.ngapblueprintdemo.events.WalletCreatedEvent, axon-metadata-traceId=589ef284-176f-49b8-aae0-0ad1588fa735, axon-message-aggregate-id=9524f7df-44fb-477f-83b8-d176583a126e}, contentLength=0, receivedDeliveryMode=PERSISTENT, redelivered=false, receivedExchange=testdemo, receivedRoutingKey=com.peaas.ngapblueprintdemo.events, deliveryTag=1, consumerTag=amq.ctag-fGm3jQcP_JIoTGf4ZMhAIg, consumerQueue=testdemo]
channel == Cached Rabbit Channel: AMQChannel(amqp://guest#127.0.0.1:5672/,1), conn: Proxy#3dcd657d Shared Rabbit Connection: SimpleConnection#19b12fd2 [delegate=amqp://guest#127.0.0.1:5672/, localPort= 52963]
You have most of the right configuration in place, but you are forgetting to tie your SpringAMQPMessageSource to an Event Processor under which your event handling component is placed.
See the reference guide for a correct example on how to reach this.
Here is a direct snippet from that reference guide to configure the message source to an event processor:
#Autowired
public void configure(EventHandlingConfiguration ehConfig, SpringAmqpMessageSource myMessageSource) {
ehConfig.registerSubscribingEventProcessor("myProcessor", c -> myMessageSource);
}
Edit
I think I see which part you where missing.
You did correctly wire the queue as a subscribable message source to an Event Processor. This follows from you application.yml, which ties the testdemo message source to the amqpEvents Event Processor. Thus sorry for my earlier assumption on that part.
The reasoning why you don't receive your events in the UserEventHandler, is because that event handler isn't tied to the amqpEvents Event Processor.
To solve that you should add the #ProcessingGroup("amqpEvents") annotation to the UserEventHandler component.
Related
I have a model:
package com.example.asyncmethod;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
#JsonIgnoreProperties(ignoreUnknown=true)
public class User {
private String name;
private String blog;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getBlog() {
return blog;
}
public void setBlog(String blog) {
this.blog = blog;
}
#Override
public String toString() {
return "User [name=" + name + ", blog=" + blog + "]";
}
}
a service call like below
package com.example.asyncmethod;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.concurrent.CompletableFuture;
#Service
public class GitHubLookupService {
private static final Logger logger = LoggerFactory.getLogger(GitHubLookupService.class);
private final RestTemplate restTemplate;
public GitHubLookupService(RestTemplateBuilder restTemplateBuilder) {
this.restTemplate = restTemplateBuilder.build();
}
#Async
public CompletableFuture<User> findUser(Integer user) throws InterruptedException {
logger.info("Looking up " + user);
String url = String.format("https://api.github.com/users/%s", user);
User results = restTemplate.getForObject(url, User.class);
Thread.sleep(1000L);
return CompletableFuture.completedFuture(results);
}
}
Async runner class:
package com.example.asyncmethod;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import java.util.concurrent.CompletableFuture;
#Component
public class AppRunner implements CommandLineRunner {
private static final Logger logger = LoggerFactory.getLogger(AppRunner.class);
private final GitHubLookupService gitHubLookupService;
public AppRunner(GitHubLookupService gitHubLookupService) {
this.gitHubLookupService = gitHubLookupService;
}
#Override
public void run(String... args) throws Exception {
// Start the clock
long start = System.currentTimeMillis();
// in real use case before the for loop I will have a database call to get the number of records and based on the size I need to call that many number of times.
for(int i=0; i <= 10; i++){
CompletableFuture<User> page1 = gitHubLookupService.findUser(1);
}
// Wait until they are all done
CompletableFuture.allOf(page1).join();
// Print results, including elapsed time
logger.info("Elapsed time: " + (System.currentTimeMillis() - start));
logger.info("--> " + page1.get());
}
}
So in the above for loop, I might call the findUser as per the number of records returned by DB.
Also in the future I might need to add few more async calls like findOrders, findInventory, findAccess etc which will call another services over http.
Considering the async call may succeed for one record and fail for another record, how can I approach here to call the async in the best possible way?
If you want to realize complete advantage of Async calls, I will suggest to avoid using CompletableFuture.get() or CompletableFuture.join().
Using this calls blocks your main thread till the time all the tasks (as part of CompletableFuture.allOf() ) are completed.
Instead you can use various functions that are provided to run a Lamba function on completion of all the futures (Also allows to handle failure in any of the Future).
Please refer the Java Docs for more details and check which method is more convenient in your implementation:
https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html
This class is not able to read values from the properties files for my spring-boot application--
This is the structure of my project-
I am able to access the values of the properties from both
application-dev.properties and config.properties in my HomeController.java class.
But I am getting the values as null in my ClientUtility.java class
HomeControlller.java
#RestController
public class HomeController {
#Autowired
private CustomerService customerService;
#Autowired
private PropertyService propertyService;
#Value("${customer.auth.key}")
private String customerAuthKey;
#Autowired
private EntityToDtoMapper mapper;
#GetMapping(path="/customer/{id}",produces= {"application/xml"})
public ResponseEntity<CustomerDto> getCustomer(#PathVariable("id")int id ,#RequestHeader("authKey") String language){
System.out.println(propertyService.getKeytoAddCustomer());
if(language.equals(customerAuthKey)) {
CustomerDto customerDto=customerService.getCustomer(id);
return new ResponseEntity<>(customerDto, HttpStatus.OK);
}
return new ResponseEntity<>(null, HttpStatus.BAD_REQUEST);
}
ClientUtility.java
#Component
public class ClientUtility {
#Value("${customer.auth.key}")
private String customerAuthKey;
#Autowired
private PropertyService propertyService;
public void getCustomers() {
String url = "http://localhost:8080/customer/1";
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
// add basic authentication header
headers.set("authKey", "6AE-BH3-24F-67FG-76G-345G-AGF6H");
System.out.println(customerAuthKey);
System.out.println(propertyService.getKeytoAddCustomer());
// build the request
HttpEntity<CustomerDto> request = new HttpEntity<CustomerDto>(headers);
ResponseEntity<CustomerDto> response = restTemplate.exchange(url, HttpMethod.GET, request, CustomerDto.class);
if (response.getStatusCode() == HttpStatus.OK) {
System.out.println("Request Successful.");
System.out.println(response.getBody().getFirstName());
} else {
System.out.println("Request Failed");
System.out.println(response.getStatusCode());
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
ClientUtility clientUtility = new ClientUtility();
clientUtility.getCustomers();
}
}
}
Output
null
Exception in thread "main" java.lang.NullPointerException
at com.spring.liquibase.demo.utility.ClientUtility.getCustomers(ClientUtility.java:33)
at com.spring.liquibase.demo.utility.ClientUtility.main(ClientUtility.java:53)
application.properties
spring.profiles.active=dev
logging.level.org.springframework.web=INFO
logging.level.com=DEBUG
local.server.port=8080
application-dev.properties
# DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties)
spring.datasource.url=jdbc:mysql://localhost:3306/liqbtest?useSSL=false
spring.datasource.username=liqbtest
spring.datasource.password=liqbtest
# Hibernate
# The SQL dialect makes Hibernate generate better SQL for the chosen database
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
# Hibernate ddl auto (create, create-drop, validate, update)
spring.jpa.hibernate.ddl-auto = update
customer.auth.key = 6AE-BH3-24F-67FG-76G-345G-AGF6H
config.properties
auth.key.to.add.customer=6AE-BH3-24F-67FG-76G-345G-AGF6H
PropertyService.class
package com.spring.liquibase.demo.utility;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
#Configuration
#PropertySource("classpath:config.properties")
public class PropertyService {
#Autowired
private Environment env;
public String getKeytoAddCustomer() {
return env.getProperty("auth.key.to.add.customer");
}
}
Here when you use ClientUtility clientUtility = new ClientUtility() it wont get autowired
so properties wont get picked up. So I would suggest to use
//Inside main method
ApplicationContext applicationContext = SpringApplication.run(ClientUtility .class, args);
ClientUtility clientUtility = applicationContext.getBean(ClientUtility.class);
clientUtility.getCustomers();
Then it should work
As TomJava already mentioned, that is a great way to use properties.
Another safe and sound way is to use the properties as a normal java bean and then utilise them.
Example:
in application-dev.properties, use
acme.enabled, with a value of false by default.
acme.remote-address, with a type that can be coerced from String.
acme.security.username, with a nested "security" object whose name is
determined by the name of the property. In particular, the return
type is not used at all there and could have been SecurityProperties.
acme.security.password.
acme.security.roles, with a collection of String that defaults to
USER.
package com.example;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
#ConfigurationProperties("acme")
public class AcmeProperties {
private boolean enabled;
private InetAddress remoteAddress;
private final Security security = new Security();
public boolean isEnabled() { ... }
public void setEnabled(boolean enabled) { ... }
public InetAddress getRemoteAddress() { ... }
public void setRemoteAddress(InetAddress remoteAddress) { ... }
public Security getSecurity() { ... }
public static class Security {
private String username;
private String password;
private List<String> roles = new ArrayList<>(Collections.singleton("USER"));
public String getUsername() { ... }
public void setUsername(String username) { ... }
public String getPassword() { ... }
public void setPassword(String password) { ... }
public List<String> getRoles() { ... }
public void setRoles(List<String> roles) { ... }
}
}
Taken from this spring documentation
Use #PropertySources Spring Annotation like follows.
#Component
#PropertySources(value={#PropertySource("classpath:application-dev.properties")})
public class ClientUtility {
#Value("${customer.auth.key}")
private String customerAuthKey;
Why can't you read the property value in Property service by Spring annotations itself?
PropertyService.class
package com.spring.liquibase.demo.utility;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
#Configuration
#PropertySource(value="classpath:config.properties")
public class PropertyService {
#Value("${auth.key.to.add.customer}")
private String authKey;
public String getKeytoAddCustomer() {
return authKey;
}
}
We would like to send actuator metrics to Cloudwatch. Using the provided micrometer cloudwatch MeterRegistry solutions makes to many assumptions about how our project is setup, for example you need to depend on cloud AWS which then makes even more assumptions. We would like to write a more lightweight implementation which just get a CloudWatchAsyncClient injected and makes no other assumptions about our project.
However im not sure how. Is there any example on how to make a custom implementation insted of having to depend on the available metrics registry?
So far I have done some experimenting with the following:
public interface CloudWatchConfig extends StepRegistryConfig {
int MAX_BATCH_SIZE = 20;
#Override
default String prefix() {
return "cloudwatch";
}
default String namespace() {
String v = get(prefix() + ".namespace");
if (v == null)
throw new MissingRequiredConfigurationException("namespace must be set to report metrics to CloudWatch");
return v;
}
#Override
default int batchSize() {
String v = get(prefix() + ".batchSize");
if (v == null) {
return MAX_BATCH_SIZE;
}
int vInt = Integer.parseInt(v);
if (vInt > MAX_BATCH_SIZE)
throw new InvalidConfigurationException("batchSize must be <= " + MAX_BATCH_SIZE);
return vInt;
}
}
#Service
#Log
public class CloudWatchMeterRegistry extends StepMeterRegistry {
public CloudWatchMeterRegistry(CloudWatchConfig config, Clock clock) {
super(config, clock);
}
#Override
protected void publish() {
getMeters().stream().forEach(a -> {
log.warning(a.getId().toString());
});
}
#Override
protected TimeUnit getBaseTimeUnit() {
return TimeUnit.MILLISECONDS;
}
}
#Configuration
public class MetricsPublisherConfig {
#Bean
public CloudWatchConfig cloudWatchConfig() {
return new CloudWatchConfig() {
#Override
public String get(String key) {
switch (key) {
case "cloudwatch.step":
return props.getStep();
default:
return "testtest";
}
}
};
}
}
However when I run the publish method is never called and no metrics are ever logged. What am I missing to get this working?
Here's an example project. I don't use cloudwatch myself so not had a chance to test it integrating with AWS. Leave a comment if there are any issues and we can try to resolve them
https://github.com/michaelmcfadyen/spring-boot-cloudwatch
I am trying to do something similar, and avoid using Spring Cloud. The simplest solution I have found so far is:
import io.micrometer.cloudwatch2.CloudWatchConfig;
import io.micrometer.cloudwatch2.CloudWatchMeterRegistry;
import io.micrometer.core.instrument.Clock;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.autoconfigure.metrics.export.properties.StepRegistryProperties;
import org.springframework.boot.actuate.autoconfigure.metrics.export.properties.StepRegistryPropertiesConfigAdapter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import software.amazon.awssdk.services.cloudwatch.CloudWatchAsyncClient;
#Configuration
public class MetricsConfiguration {
#Bean
public CloudWatchMeterRegistry cloudWatchMeterRegistry(CloudWatchConfig config, Clock clock) {
return new CloudWatchMeterRegistry(config, clock, CloudWatchAsyncClient.create());
}
#Component
public static class MicrometerCloudWatchConfig
extends StepRegistryPropertiesConfigAdapter<StepRegistryProperties>
implements CloudWatchConfig {
private final String namespace;
private final boolean enabled;
public MicrometerCloudWatchConfig(
#Value("${CLOUDWATCH_NAMESPACE}") String namespace,
#Value("${METRICS_ENABLED}") boolean enabled) {
super(new StepRegistryProperties() {
});
this.namespace = namespace;
this.enabled = enabled;
}
#Override
public String namespace() {
return namespace;
}
#Override
public boolean enabled() {
return enabled;
}
#Override
public int batchSize() {
return CloudWatchConfig.MAX_BATCH_SIZE;
}
}
}
Dependencies:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-cloudwatch2</artifactId>
</dependency>
I am creating a webserver on my Android device, and everything seems to be working fine except for the fact Jackson is not converting my objects to JSON. When I make a successfull (200) HTTP request from the browser on my Android device I am getting no JSON data in the network tab (Looking at the response in Google Chrome). I have registered the Jackson Converter as the documents state, and as what previous StackOverflow questions have covered. This does not seem to work, and I'm not getting any error. Any idea what I'm doing wrong? I hope this is enough information for you geniuses here, otherwise let me know what else you guys need.
Note I also tried GSONConverter
Edit: I've noticed that the entity inside the response is always null
WebServer.java
package com.android.restlettest;
import org.restlet.Component;
import org.restlet.data.Protocol;
public class WebServer {
Component component;
WebServer() {
component = new Component();
component.getServers().add(Protocol.HTTP, 9001);
component.getDefaultHost().attach("", new MyApplication());
}
void start() {
try {
System.out.println("Starting the WebServer");
component.start();
} catch(Exception e) {
System.out.println("Exception caught: " + e);
}
}
void stop() {
}
}
MyApplication.java
package com.android.restlettest;
import org.restlet.Application;
import org.restlet.Restlet;
import org.restlet.routing.Router;
import org.restlet.engine.Engine;
import org.restlet.ext.gson.GsonConverter;
import org.restlet.ext.jackson.JacksonConverter;
public class MyApplication extends Application {
MyApplication() {
super();
Engine.getInstance().getRegisteredConverters().add(new JacksonConverter());
//Engine.getInstance().getRegisteredConverters().add(new GsonConverter());
}
#Override
public synchronized Restlet createInboundRoot() {
Router router = new Router(getContext());
router.attach("/", IndexResource.class);
router.attach("/report/log", LogServerResource.class);
return router;
}
}
LogServerResource.java
public class LogServerResource extends ServerResource implements ILogResource {
#Get("json")
public Log retrieve() {
Log[] list = new Log[2];
Log log1 = new Log("test.log", "alksdjf32984u23jfsdv", 0);
list[0] = log1;
return list[0];
//return "{\"resource\": \"log\"}";
}
}
Log.java
package com.android.restlettest;
import java.io.Serializable;
public class Log implements Serializable {
private static final long serialVersionUID = 1L;
public String data;
public String name;
public int id;
public Log() {
}
public Log(final String name, final String data, final int id) {
super();
this.name = name;
this.data = data;
this.id = id;
}
}
I ended up finding someone that had a similar issue to me. I was missing some libs that needed to be added. I apologize for wasting your time.
Restlet server on Android: client is getting a null object
I have an actor class EmployeeActor, inside that actor, some other actor is fired using payrollRunActor.tell(). I need to write a JUnit test for EmployeeActor.java, but I don't want to fire payrollRunActor.tell(), means I want to mock it.
Is there a way to do it? I tried a lot, but real payrollRunActor is getting fired.
Here is the actual code of my EmployeeActor class.
package com.test.periodic.actors;
import java.util.List;
import org.apache.commons.lang3.RandomStringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.test.avs.domain.boundedcontext.Employee;
import com.test.avs.domain.boundedcontext.PayrollRun;
import com.test.entity.BusinessDTO;
import com.test.periodic.actors.aggregrators.EmployeeAggregator;
import akka.actor.AbstractActor;
import akka.actor.ActorRef;
import akka.actor.ActorSystem;
import akka.actor.Props;
import akka.routing.RoundRobinPool;
public class EmployeeActor extends AbstractActor {
private static final Logger logger = LoggerFactory.getLogger(EmployeeActor.class);
private boolean rollup;
public static Props props() {
return Props.create(EmployeeActorTest.class);
}
private List<PayrollRun> payrollRuns;
private String instanceId;
private String employeeAggregatorId;
private Employee employee;
private ActorRef organizationAggregatorActor;
private List<BusinessDTO> businessDTOs;
final ActorSystem payrollRunSystem = ActorSystem.create("payrollRun");
ActorRef employeeAggregator;
public EmployeeActor(ActorRef organizationAggregatorActor, List<PayrollRun> payrollRuns,
Employee employee, List<BusinessDTO> businessDTOs, boolean rollup) {
this.payrollRuns = payrollRuns;
this.employee = employee;
this.organizationAggregatorActor = organizationAggregatorActor;
this.businessDTOs = businessDTOs;
this.rollup = rollup;
}
#Override
public void preStart() throws Exception {
instanceId = RandomStringUtils.randomAlphanumeric(6);
employeeAggregatorId = "employeeAggregator-" + instanceId;
employeeAggregator = getContext().system().actorOf(
Props.create(EmployeeAggregator.class, organizationAggregatorActor, employee),
employeeAggregatorId);
super.preStart();
}
#Override
public Receive createReceive() {
return receiveBuilder().match(Employee.class, employee -> {
if (rollup) {
logger.info("Rollingup business entities.");
employeeAggregator.tell(employee, getSelf());
} else {
ActorRef payrollRunActor = payrollRunSystem.actorOf(new RoundRobinPool(payrollRuns.size())
.props(Props.create(PayrollRunActor.class, employeeAggregator, employee, businessDTOs)));
for (PayrollRun payrollRun : payrollRuns) {
**payrollRunActor.tell(payrollRun, getSelf());**
}
}
}).match(PayrollRun.class, maxPaydatePayrollRun -> {
ActorRef payrollRunActor = payrollRunSystem
.actorOf(Props.create(PayrollRunActor.class, employeeAggregator, employee, businessDTOs));
**payrollRunActor.tell(maxPaydatePayrollRun, getSelf());**
}).build();
}
}
First of all you would have to mock the static method call which is invoked during the creation of class under test. Then make it return a spied object and mock the method you want to avoid calling:
#RunWith(PowerMockRunner.class)
#PrepareForTest(ActorSystem.class)
public void TestClass{
#Test
public void test(){
// Arrange
PowerMockito.mockStatic(ActorSystem.class);
ActorSystem actorSystemMock = Mockito.mock(ActorSystem.class);
Actor actorSpy = Mockito.spy(new Actor());
Mockito.when(ActorSystem.create("payrollRun")).thenReturn(actorSystemSpy);
Mockito.when(actorSystemMock.actorOf(any(RoundRobinPool.class)))
.thenReturn(actorSpy);
Mockito.doNothing().when(actorSpy)
.tell(Mockito.any(PayrollRun.class), Mockito.any(Self.class));
EmployeeActor employeeActor = new EmployeeActor();
// Act and assert...
employeeActor.createReceive();
}
}
Remember that all other methods of actorSystemSpy will be called will real implementation. If you want to mock all of them then use Mockito.mock instead of spy.