TL;DR I think mutation should be killed but it survived. I am looking for the reason why it's happening and how to fix these 2 things: (1) Timeout (2) mutation survived.
Details I have a spring web application and am testing using testng. I have extracted the relevant part of the code. Please excuse me if I have introduced any problem while extracting the code for this question. I have a passing test case which verifies that the callFunction is called 8 times. This is verified using verify(a, atLeast(8)).called(); After seeing the piTest report it seems that if the callFunction is removed the function will still have a.called(); 8 times ... which is unexpected.
I have checked by removing callFunction from the source and the test case does fail. See the section Modified1 Rat.java.
Also I have checked by removing the forEach and the test case does fail. See Modified2 Rat.java.
There is an even interesting thing that when I changed only the (formatting) location of the text in Rat.java like shown in Modified3 Rat.java section the piTest report changed.
Type.java
package lab.rat;
public class Type {
}
Action.java
package lab.rat;
import org.springframework.stereotype.Component;
#Component public class Action {
public void called() {}
}
Rat.java
package lab.rat;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.IntStream;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
#Component public class Rat {
#Autowired private Action a;
public void testee() {
Map<Type, Integer> properties = new HashMap<>();
IntStream
.range(0, 8)
.forEach(index -> properties.put(new Type(), index));
properties
.entrySet()
.stream()
NOTICE FOLLOWING LINE
.forEach(entry -> callFunction()); // removed call to lab/rat/Rat::callFunction ? TIMED_OUT
// removed call to java/util/stream/Stream::forEach ? SURVIVED
}
private void callFunction() {
a.called();
}
}
RatTest.java
package lab.rat;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.verify;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import lab.rat.config.SpringConfigurationForTest;
public class RatTest extends SpringConfigurationForTest {
#InjectMocks Rat rat;
#Mock Action a;
#BeforeMethod public void setup() {
MockitoAnnotations.initMocks(this);
}
#Test public void testTestee() {
rat.testee();
verify(a, atLeast(8)).called();
}
}
Modified1 Rat.java -- test fails
.stream()
.forEach(entry -> {});
Modified2 Rat.java -- test fails
.stream();
Modified3 Rat.java -- one more mutation created
.stream()
.forEach( // removed call to java/util/stream/Stream::forEach ? SURVIVED
// removed call to lab/rat/Rat::callFunction ? TIMED_OUT
entry -> callFunction() // replaced return of integer sized value with (x == 0 ? 1 : 0) ? KILLED
);
Years later but no one seemed to mention that (Spring) #Component and (Mockito) #InjectMocks are mutually exclusive solutions. You have multiple generated subclasses of Rat in play so PIT is simply confused about what's going on. It probably mutated the wrong one.
Related
Small question regarding SpringBoot + Spring Cloud function application please.
I have a very simple app which takes a word as input.
The business logic is written via functions. The "difficulty" is: the functions need to be configurable. Meaning, with just an application.properties change + restart, the web app should be able to pick the correct business logic, i.e. the correct function composition to apply to the input, without code change.
Here is an example:
application.properties:
spring.cloud.function.definition=upper
#spring.cloud.function.definition=reverse
#spring.cloud.function.definition=upper|reverse
pom:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-function-context</artifactId>
<version>4.0.0</version>
</dependency>
</dependencies>
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Service;
import java.util.function.Function;
#Service
public class ReverseFunction {
#Bean
Function<String, String> reverse() {
return s -> new StringBuilder(s).reverse().toString();
}
}
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Service;
import java.util.function.Function;
#Service
public class UpperFunction {
#Bean
Function<String, String> upper() {
return String::toUpperCase;
}
}
And this is the business logic:
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.function.Function;
#RestController
public class FunctionController {
#GetMapping("/applyFunctionToWord")
public String applyFunctionToWord(#RequestParam String word) {
Function<String, String> function = ??? //configurable from application.properties
return function.apply(word);
}
}
In tutorials found online and from official documentation from Spring, it is said:
Function Composition
Function composition is a feature of SCF that lets you compose several functions together in a declarative way.
The following example shows how to do so:
--spring.cloud.function.definition=uppercase|reverse
Here, we effectively provided a definition of a single function that is itself a composition of a function named uppercase and a function named reverse. You can also argue that we’ve orchestrated a simple pipeline consisting of running the uppercase function and then sending its output to the reverse function. The term orchestration is important here, and we cover it in more detail later in the post.
https://spring.io/blog/2019/11/04/spring-cloud-stream-composed-functions-or-eip#:~:text=Function%20composition%20is%20a%20feature%20of%20SCF%20that,following%20example%20shows%20how%20to%20do%20so%3A%20--spring.cloud.function.definition%3Duppercase%7Creverse
What I tried:
I did configure the property in application.properties.
And as you can see, I did follow the guide naming the property: spring.cloud.function.definition. What is the correct machanism please?
Am I forced to fallback to something with #Value?
My app is not a Spring Cloud Stream app. It is not based on Kafka. It is just a basic web app where the business logic is a configurable composition functions.
Question:
How to have the webapp take into effect the value configured in spring.cloud.function.definition=, and apply it to the input please?
Thank you
I would assume it should work using RoutingFunction, something like this:
#RestController
public class FunctionController {
#Autowired
RoutingFunction function;
#GetMapping("/applyFunctionToWord")
public String applyFunctionToWord(#RequestParam String word) {
return (String)function.apply(word);
}
}
You need a #Value annotation and some logic like this
#RestController
public class FunctionController {
#Value("${spring.cloud.function.definition}")
String definition;
#Autowired
#Qualifier("reverse");
Function reverse;
#Autowired
#Qualifier("upper");
Function upper;
#GetMapping("/applyFunctionToWord")
public String applyFunctionToWord(#RequestParam String word) {
Function<String, String> function = null
switch (definition) {
case "upper" :
function = upper;
break;
case "reverse" :
function = reverse;
break;
}
return function.apply(word);
}
}
I am struggeling to log an object's property (for example its id) received by Flux/Mono by using the MDC in an reactive environment.
I know how to put data from the reactors context to MDC.
Unfortunatly I am unable to put a custom dynamically data to context.
This faked screenshot should show what I want to achieve - the red information should be added.
Below there is a reduced example code. logback is configured well. the "id=x" output is missing and I ask for help how to achieve this. The value for the id is coming from the Flux directly unlike the "static context key" app.
import org.slf4j.MDC;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.publisher.Signal;
import reactor.util.context.Context;
import javax.annotation.PostConstruct;
import java.util.Optional;
import java.util.function.Consumer;
#Service
public class TestService {
#PostConstruct
public void postConstruct2() {
Flux<TestObject> messages = Flux.just(
new TestObject(1, "First"),
new TestObject(2, "Second")
);
messages
.flatMap(to -> Mono
.deferContextual(ctx -> Mono.just(to))
.contextWrite(c -> c.put("id", to.getId()))
)
.doOnEach(logOnNext(r -> L.info("new object: incoming 1")))
.map(to -> to.getName())
.doOnEach(logOnNext(r -> L.info("new object: incoming 2")))
.map(name -> name.toUpperCase())
.doOnEach(logOnNext(r -> L.info("new object: incoming 3")))
.contextWrite(c -> c.put("app", "test"))
.subscribe(s -> System.out.println(s))
;
}
public static <T> Consumer<Signal<T>> logOnNext(Consumer<T> logStatement) {
return signal -> {
if (!signal.isOnNext()) return;
var view = signal.getContextView();
view.forEach((k, v) -> {
System.out.println(k);
MDC.put(k + "", v + "");
});
logStatement.accept(signal.get());
};
}
}
How to implement customer listeners in cucumber?
which can log to the console/report about the occurrence of the failed method?
using cucumber 4.0
Note: Hooks are not helping at method levels
No custom listener option in cucumber like TestNG. we should have use only Hooks.
You can implement the ConcurrentEventListener to get certain hooks for events [example].
Events can be found here
You can implement listeners in cucumber like below
import io.cucumber.plugin.EventListener;
import io.cucumber.plugin.event.*;
import java.net.URI;
import java.util.Map;
import java.util.TreeMap;
import java.util.UUID;
public class ReportPlugin implements EventListener {
private final Map<String, UUID> startedSteps = new TreeMap<String, UUID>();
private final Map<String, Status> finishedCases = new TreeMap<String, Status>();
#Override
public void setEventPublisher(EventPublisher publisher) {
publisher.registerHandlerFor(TestStepStarted.class, this::handleTestStepStarted);
publisher.registerHandlerFor(TestCaseFinished.class, this::handleTestCaseFinished);
}
private void handleTestStepStarted(TestStepStarted event) {
startedSteps.put(event.getTestStep().toString(), event.getTestStep().getId());
for (Map.Entry<String, UUID> entry : startedSteps.entrySet()) {
String location = entry.getKey();
UUID uuid = entry.getValue();
System.out.println(location + " ###fromTestStepStarted### " + uuid);
//above prints
//io.cucumber.core.runner.PickleStepTestStep#5a5c128 ###fromTestStepStarted### 7f964f1c-9442-43fc-97e9-9ec6717eb47f
// io.cucumber.core.runner.PickleStepTestStep#77b919a3 ###fromTestStepStarted### a5d57753-aecb-40a0-a0cf-76bef7526dd8
}
}
}
To run the above class - put the class with your step defs or your supporting classes and then in junit-platform.properties (for Junit5) mention plugin like this
cucumber.plugin = com.test.support.ReportPlugin
For Junit4 you might have to add plugin to your runner class
When you run your tests you should see everything printed on console
I have a class:
public class RequestHandler implements HttpHandler {
public void handleRequest(HttpServerExchange serverContext) throws Exception {
serverContext.dispatch(() -> serverContext.getRequestReceiver()
.receiveFullBytes((httpServerExchange, reqBytes) -> {
// business logic along with few function call
}
)
);
}
}
I want to write a unit test case to test my business logic. I am not sure how to do it with 2 levels of a lambda expression insider a dispatcher? Can someone please suggest a good way to write test cases?
I know that we can move business logic to new class and can test it (i guess it's better designed) but curious to know what if it's part of some legacy code or something that we can't change, how can we test it?
Under the assumption that somewhere in your buisness logic you forward the received message (or whatever you do with it) to somewhere else, you can just test your code as usual.
Note that HttpServerExchange is a final class, so you need to use a Mockito version that supports final mocking - and you have to enable it, as described here.
To get around the lambda expression you need to use thenAnswer or doAnswer to trigger the invocation of the correct interface method manually.
A simple example could look like this:
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.stubbing.Answer;
import io.undertow.io.Receiver;
import io.undertow.io.Receiver.FullBytesCallback;
import io.undertow.server.HttpHandler;
import io.undertow.server.HttpServerExchange;
#ExtendWith(MockitoExtension.class)
public class RequestHandlerTest {
static class BuisnessLogic {
public void someMethod(HttpServerExchange httpServerExchange, byte[] reqBytes) {
}
}
static class RequestHandler implements HttpHandler {
BuisnessLogic logic;
public void handleRequest(HttpServerExchange serverContext) throws Exception {
serverContext.dispatch(
() -> serverContext.getRequestReceiver().receiveFullBytes(
(httpServerExchange, reqBytes) -> {
logic.someMethod(httpServerExchange, reqBytes);
}
)
);
}
}
#Mock
BuisnessLogic logic;
#InjectMocks
RequestHandler handler;
#Test
public void test() throws Exception {
byte[] message = new byte[] {1,2,3};
HttpServerExchange serverContext = Mockito.mock(HttpServerExchange.class);
// 1st lambda
Mockito.when(serverContext.dispatch(Mockito.any(Runnable.class)))
.thenAnswer((Answer<HttpServerExchange>) invocation -> {
Runnable runnable = invocation.getArgument(0);
runnable.run();
return serverContext;
});
// 2nd lambda
Receiver receiver = Mockito.mock(Receiver.class);
Mockito.doAnswer((Answer<Void>) invocation -> {
FullBytesCallback callback = invocation.getArgument(0);
callback.handle(serverContext, message);
return null;
}).when(receiver).receiveFullBytes(Mockito.any(FullBytesCallback.class));
Mockito.when(serverContext.getRequestReceiver()).thenReturn(receiver);
// class under test - method invocation
handler.handleRequest(serverContext);
// buisness logic call verification
ArgumentCaptor<HttpServerExchange> captor1 = ArgumentCaptor.forClass(HttpServerExchange.class);
ArgumentCaptor<byte[]> captor2 = ArgumentCaptor.forClass(byte[].class);
Mockito.verify(logic).someMethod(captor1.capture(), captor2.capture());
Assertions.assertEquals(serverContext, captor1.getValue());
Assertions.assertEquals(message, captor2.getValue());
}
}
As others already mentioned you should only use that approach for legacy code.
A simple refactoring could just push the entire part you need to test into its own method, which - in the example above - would just be the buisness logic itself.
There is no explicit need to test the undertow framework yourself.
I've hit a wall using Objectify for the google appengine datastore when filtering on boolean values. This is roughly what I've:
class Task implements Serializable {
...
boolean failed;
...
}
No matter what i do when i search, i always get an empty response although there are objects in the db that has failed = false
Examples:
ofy().query(Task.class).filter("failed",false).list()
ofy().query(Task.class).filter("failed",Boolean.FALSE).list()
ofy().query(Task.class).filter("failed",0).list()
ofy().query(Task.class).filter("failed","false").list()
ofy().query(Task.class).filter("failed","FALSE").list()
I found this old question while Googling and I wanted to clear it up.
You should be able to query by boolean fields as long as they are indexed at the time that they entered the datastore. Here's a complete unit test using Objectify and the App Engine unit test library (To run it, you have to link in the unit test jar described here). The following test passes. So the problem lies elsewhere, and I suggest that you use unit tests to discover it.
import static org.junit.Assert.*;
import javax.persistence.Id;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.google.appengine.api.datastore.QueryResultIterator;
import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig;
import com.google.appengine.tools.development.testing.LocalServiceTestHelper;
import com.googlecode.objectify.Objectify;
import com.googlecode.objectify.ObjectifyFactory;
import com.googlecode.objectify.ObjectifyService;
import com.googlecode.objectify.Query;
class FakeEntity {
#Id public Long id;
public boolean boolProp;
public boolean equals(Object other) {
return other != null &&
other instanceof FakeEntity &&
((FakeEntity)other).id == this.id &&
((FakeEntity)other).boolProp == this.boolProp;
}
}
public class FakeEntityTest {
private final LocalServiceTestHelper helper =
new LocalServiceTestHelper(new LocalDatastoreServiceTestConfig());
#Before
public void setUp() {
helper.setUp();
}
#After
public void tearDown() {
helper.tearDown();
}
#Test
public void testBoolQuery() {
ObjectifyFactory objectifyFactory = ObjectifyService.factory();
objectifyFactory.register(FakeEntity.class);
Objectify objectify = objectifyFactory.begin();
FakeEntity entityFalse = new FakeEntity();
FakeEntity entityTrue = new FakeEntity();
entityTrue.boolProp = true;
objectifyFactory.begin().put(entityFalse);
objectifyFactory.begin().put(entityTrue);
assertArrayEquals(
new FakeEntity[] {entityFalse},
objectify.query(FakeEntity.class)
.filter("boolProp", false).list().toArray());
assertArrayEquals(
new FakeEntity[] {entityTrue},
objectify.query(FakeEntity.class)
.filter("boolProp", true).list().toArray());
assertArrayEquals(
new FakeEntity[] {entityTrue},
objectify.query(FakeEntity.class)
.filter("boolProp", true).list().toArray());
assertArrayEquals(
new FakeEntity[] {entityTrue},
objectify.query(FakeEntity.class)
.filter("boolProp", Boolean.TRUE).list().toArray());
// Filtering on integers and strings WON'T work:
assertArrayEquals(
new FakeEntity[] {},
objectify.query(FakeEntity.class)
.filter("boolProp", "true").list().toArray());
assertArrayEquals(
new FakeEntity[] {},
objectify.query(FakeEntity.class)
.filter("boolProp", 0).list().toArray());
}
}
You haven't Indexed boolean failed property.
If a field is not indexed, filter will not work in objectify datastore.
So to make it work, add
#Index boolean failed;
Now your filter will work.
Please note that though indexed, already saved values cannot be filtered. So either create new records and save or read all datastore entities and save it again.
Hope this helps.