Hot questions for Using Mockito in apache camel

Hot questions for Using Mockito in apache camel

Top 10 Java Open Source / Mockito / apache camel

Question:

I have encountered a problem when I was tasked with creating JUnit test to one of my camel processor.

The main class is as follows: (redundant things omitted).

@Stateless
@Named
public class CalculateProportionalAmount implements Plugin{ // where Plugin is our interface extending a Processor
LocalDate now;

@Override
public void process(final Exchange exchange) throws Exception{
    now = getNow();
    int startDay = getValueOfInputParameter("start") //just gets 1-31 from input parameters on exchange
    int value = getValueOfInputParameter("value") //gets value from input parameters
/*
More kind-of irrelevant lines of code. Idea is that the processor calculates number of days between "now" and startDay, calculates what proportion of month this amount of days is and applies this proportion to the value. 

So if today would be 1st and startDay is 10th (so 10 days between) when September has 30 days and value = 1000, the processor would calculate (10/30)*1000 
*/
}

public LocalDate getNow(){
    return LocalDate.now();
}
}

And for the test class:

public class CalculateProportionalAmountTest{
Plugin plugin; 

@Before
public void setUp(){
   //inicialize parameter maps, instantiate the plugin, so that we can reference it. "plugin" value is then instance of the "CalculateProportionalAmount" class.
}

    @Test
    public void pluginTestNextMonth() throws Exception {

        Mockito.when(((CalculateProportionalAmount) plugin).getNow()).thenReturn(LocalDate.of(2017, 12, 11)); //obviously does not work, because plugin is not mocked.... 

        ruleParameter.put("start", "10"); //here we set the "start" param that processor class gets.
        ruleParameter.put("value", "1000"); //here we set the "value" param that processor class gets.
        Exchange prepareInput = prepareExchange(scenarioParameters, ruleParameter);
        Exchange output = execute(prepareInput);

        String resultString = getScenarioParameterByKey(output, "result");
        TestCase.assertEquals(String.format("%.6f", Double.valueOf(1000) * 30 / 31), resultString); //obviously will not pass unless on 11th of December
    }
}

My biggest problem is that the getNow() method is and has to be called inside the process method, overwriting any attempts to specify a date.

Calculating the "real" proportions is also not viable option as we need to be able to check for variants "later this month", "earlier this month" and "today" on any day.

As the most feasible solution I now have is to rig (mock) the getNow() method to return a specific date when called from the test, but I need to leave the process method to be working as written, without any mocks.

The project this is part of already uses Mockito, but I am not very skilled in how it works and how to correctly mock the class so that it works as described above. I already made an attempt to do so in the beginning of the test class, but it currently ends in exception and I have been browsing tutorials since without much luck.

Thank you for help


Answer:

You can use @Spy i think.

@Spy
Plugin plugin; 

And then in your test method you can manipulate with doReturn for this public method

Mockito.doReturn(LocalDate.of(2017, 12, 11)).when((CalculateProportionalAmount) plugin).getNow();

@Spy tag refer to real object and you can change the return methods of spy object.

Question:

I'm having some trouble getting a working Camel Spring-Boot unit test written, that tests a simple SNMP route. Here is what I have so far:

SnmpRoute.kt

open class SnmpRoute(private val snmpProperties: SnmpProperties, private val repository: IPduEventRepository) : RouteBuilder() {

    @Throws(Exception::class)
    override fun configure() {

        logger.debug("Initialising with properties [{}]", snmpProperties)

        from("snmp:0.0.0.0:1161?protocol=udp&type=TRAP")
                .process { exchange ->
                    // do stuff
                }
                .bean(repository, "save")
    }
}

SnmpRouteTest.kt

@CamelSpringBootTest
@SpringBootApplication
@EnableAutoConfiguration
open class SnmpRouteTest : CamelTestSupport() {

    object SnmpConstants {
        const val SNMP_TRAP = "<snmp><entry><oid>...datadatadata...</oid><value>123456</value></entry></snmp>"
        const val MOCK_SNMP_ENDPOINT = "mock:snmp"
    }

    @Mock
    lateinit var snmpProperties: SnmpProperties

    @Mock
    lateinit var repository: IPduEventRepository

    @InjectMocks
    lateinit var snmpRoute: SnmpRoute

    @EndpointInject(SnmpConstants.MOCK_SNMP_ENDPOINT)
    lateinit var mock: MockEndpoint

    @Before
    fun setup() {
        initMocks(this)
    }

    @Throws(Exception::class)
    override fun createRouteBuilder(): RouteBuilder {
        return snmpRoute
    }

    @Test
    @Throws(Exception::class)
    fun `Test SNMP endpoint`() {
        mock.expectedBodiesReceived(SnmpConstants.SNMP_TRAP)
        template.sendBody(SnmpConstants.MOCK_SNMP_ENDPOINT,
                          SnmpConstants.SNMP_TRAP)
        mock.assertIsSatisfied()

        verify(repository).save(PduEvent(1234, PDU.TRAP))
    }
}

However, when I run this test, it fails as the repository mock never has any interactions:

Wanted but not invoked:
repository.save(
    PduEvent(requestId=1234, type=-89)
);
-> at org.meanwhile.in.hell.camel.snmp.route.SnmpRouteTest.Test SNMP endpoint(SnmpRouteTest.kt:61)
Actually, there were zero interactions with this mock.

Can someone help me understand why this isn't interacting correctly? When run manually, this works and saves as expected.


Answer:

Now I see what is going on here! Your RouteBuilder under test has a from("snmp"). If you wish to deliver a mock message there for testing, you need to swap the snmp: component with something like a direct: or seda: component, during test execution.

Your current test is delivering a message to a Mock endpoint and verifying if it was received there. It does not interact with the real route builder. That's why your mock endpoint assertions do passed but Mockito.verify() failed.

TL;DR

Presuming that you are using Apache Camel 3.x, here is how to do it. I'm not fluent in Kotlin so, I'll show how to do that in Java.

AdviceWithRouteBuilder.adviceWith(context, "route-id", routeBuilder -> {
  routeBuilder.replaceFromWith("direct:snmp-from"); //Replaces the from part of the route `route-id` with a direct component
});
  1. You need to modify your route builder code to assign an ID to the route (say, route-id)
  2. Replace the SNMP component at the start of the route with a direct component
  3. Deliver test messages to the direct: component instead of SNMP

TL;DR ends.

Full blown sample code below.

PojoRepo.java

@Component
public class PojoRepo {

    public void save(String body){
        System.out.println(body);
    }
}

SNMPDummyRoute.java

@Component
public class SNMPDummyRoute extends RouteBuilder {

    PojoRepo pojoRepo;
    public SNMPDummyRoute(PojoRepo pojoRepo) {
        this.pojoRepo = pojoRepo;
    }
    @Override
    public void configure() throws Exception {
        from("snmp:0.0.0.0:1161?protocol=udp&type=TRAP")
                .id("snmp-route")
                .process(exchange -> {
                    exchange.getMessage().setBody(String.format("Saw message [%s]", exchange.getIn().getBody()));
                })
                .to("log:snmp-log")
                .bean(pojoRepo, "save");
    }
}

SNMPDummyRoteTest.java

Note: This class uses CamelSpringBootRunner instead of extending CamelTestSupport, but the core idea is same.

@RunWith(CamelSpringBootRunner.class)
@SpringBootTest
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
@DisableJmx(false)
@MockEndpoints("log:*")
public class SNMPDummyRouteTest {

    @MockBean
    PojoRepo repo;

    @EndpointInject("mock:log:snmp-log")
    MockEndpoint mockEndpoint;

    @Produce
    ProducerTemplate testTemplate;

    @Autowired
    CamelContext camelContext;


    @Test
    public void testRoute() throws Exception {

        AdviceWithRouteBuilder.adviceWith(camelContext,"snmp-route",routeBuilder -> {
            routeBuilder.replaceFromWith("direct:snmp-from");
        });

        testTemplate.sendBody("direct:snmp-from","One");
        testTemplate.sendBody("direct:snmp-from","Two");

        mockEndpoint.expectedMinimumMessageCount(2);
        mockEndpoint.setAssertPeriod(2_000L);

        mockEndpoint.assertIsSatisfied();
        Mockito.verify(repo, Mockito.atLeast(2)).save(anyString());
    }

}

Logs from test run below. Take a closer look at the XML piece where the SNMP endpoint gets swapped in with a direct component.

2019-11-12 20:52:57.126  INFO 32560 --- [           main] o.a.c.component.snmp.SnmpTrapConsumer    : Starting trap consumer on udp:0.0.0.0/1161
2019-11-12 20:52:58.363  INFO 32560 --- [           main] o.a.c.component.snmp.SnmpTrapConsumer    : Started trap consumer on udp:0.0.0.0/1161 using udp protocol
2019-11-12 20:52:58.364  INFO 32560 --- [           main] o.a.c.s.boot.SpringBootCamelContext      : Route: snmp-route started and consuming from: snmp://udp:0.0.0.0/1161
2019-11-12 20:52:58.368  INFO 32560 --- [           main] o.a.c.s.boot.SpringBootCamelContext      : Total 1 routes, of which 1 are started
2019-11-12 20:52:58.370  INFO 32560 --- [           main] o.a.c.s.boot.SpringBootCamelContext      : Apache Camel 3.0.0-M4 (CamelContext: MyCamel) started in 2.645 seconds
2019-11-12 20:52:59.670  INFO 32560 --- [           main] o.a.c.i.engine.DefaultShutdownStrategy   : Starting to graceful shutdown 1 routes (timeout 10 seconds)
2019-11-12 20:52:59.680  INFO 32560 --- [ - ShutdownTask] o.a.c.component.snmp.SnmpTrapConsumer    : Stopped trap consumer on udp:0.0.0.0/1161
2019-11-12 20:52:59.683  INFO 32560 --- [ - ShutdownTask] o.a.c.i.engine.DefaultShutdownStrategy   : Route: snmp-route shutdown complete, was consuming from: snmp://udp:0.0.0.0/1161
2019-11-12 20:52:59.684  INFO 32560 --- [           main] o.a.c.i.engine.DefaultShutdownStrategy   : Graceful shutdown of 1 routes completed in 0 seconds
2019-11-12 20:52:59.687  INFO 32560 --- [           main] o.a.c.s.boot.SpringBootCamelContext      : Route: snmp-route is stopped, was consuming from: snmp://udp:0.0.0.0/1161
2019-11-12 20:52:59.689  INFO 32560 --- [           main] o.a.c.s.boot.SpringBootCamelContext      : Route: snmp-route is shutdown and removed, was consuming from: snmp://udp:0.0.0.0/1161
2019-11-12 20:52:59.691  INFO 32560 --- [           main] o.apache.camel.builder.AdviceWithTasks   : AdviceWith replace input from [snmp:0.0.0.0:1161?protocol=udp&type=TRAP] --> [direct:snmp-from]
2019-11-12 20:52:59.692  INFO 32560 --- [           main] org.apache.camel.reifier.RouteReifier    : AdviceWith route after: Route(snmp-route)[From[direct:snmp-from] -> [process[Processor@0x589dfa6f], To[log:snmp-log], Bean[org.foo.bar.POJORepo$MockitoMock$868728200]]]
2019-11-12 20:52:59.700  INFO 32560 --- [           main] org.apache.camel.reifier.RouteReifier    : Adviced route before/after as XML:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<route xmlns="http://camel.apache.org/schema/spring" customId="true" id="snmp-route">
    <from uri="snmp:0.0.0.0:1161?protocol=udp&amp;type=TRAP"/>
    <process id="process1"/>
    <to id="to1" uri="log:snmp-log"/>
    <bean id="bean1" method="save"/>
</route>

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<route xmlns="http://camel.apache.org/schema/spring" customId="true" id="snmp-route">
    <from uri="direct:snmp-from"/>
    <process id="process1"/>
    <to id="to1" uri="log:snmp-log"/>
    <bean id="bean1" method="save"/>
</route>

2019-11-12 20:52:59.734  INFO 32560 --- [           main] .i.e.InterceptSendToMockEndpointStrategy : Adviced endpoint [log://snmp-log] with mock endpoint [mock:log:snmp-log]
2019-11-12 20:52:59.755  INFO 32560 --- [           main] o.a.c.s.boot.SpringBootCamelContext      : Route: snmp-route started and consuming from: direct://snmp-from
2019-11-12 20:52:59.834  INFO 32560 --- [           main] snmp-log                                 : Exchange[ExchangePattern: InOnly, BodyType: String, Body: Saw message [One]]
2019-11-12 20:52:59.899  INFO 32560 --- [           main] snmp-log                                 : Exchange[ExchangePattern: InOnly, BodyType: String, Body: Saw message [Two]]
2019-11-12 20:52:59.900  INFO 32560 --- [           main] o.a.camel.component.mock.MockEndpoint    : Asserting: mock://log:snmp-log is satisfied
2019-11-12 20:53:01.903  INFO 32560 --- [           main] o.a.camel.component.mock.MockEndpoint    : Re-asserting: mock://log:snmp-log is satisfied after 2000 millis
2019-11-12 20:53:01.992  INFO 32560 --- [           main] o.a.c.s.boot.SpringBootCamelContext      : Apache Camel 3.0.0-M4 (CamelContext: MyCamel) is shutting down
2019-11-12 20:53:01.993  INFO 32560 --- [           main] o.a.c.i.engine.DefaultShutdownStrategy   : Starting to graceful shutdown 1 routes (timeout 10 seconds)
2019-11-12 20:53:01.996  INFO 32560 --- [ - ShutdownTask] o.a.c.i.engine.DefaultShutdownStrategy   : Route: snmp-route shutdown complete, was consuming from: direct://snmp-from
2019-11-12 20:53:01.996  INFO 32560 --- [           main] o.a.c.i.engine.DefaultShutdownStrategy   : Graceful shutdown of 1 routes completed in 0 seconds