As a test developer I want to have the ability to test some components in multiple types of environment.
Some environments would be with mocks. Some would mock only data-layers and create all components in this test JVM. Some environments would be as docker containers plus real machines hybrid. You name it!!!
But as the tests writer - I truly don't care.
So what's the plan?
let's say we have two interfaces:
package io.scalecube.test.fixtures;
@FunctionalInterface
public interface EchoService {
String echo(String s);
}
And
@FunctionalInterface
public interface PalindromeService {
boolean palindrome(String s);
}
Both of them are in the system under test.
package io.scalecube.test.fixtures;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.TestTemplate;
import org.junit.jupiter.api.extension.ExtendWith;
@ExtendWith(Fixtures.class)
@WithFixture(SlowServiceFixture.class)
@WithFixture(FasterServiceFixture.class)
public class BaseTest {
@TestTemplate
public void test(EchoService echoService, PalindromeService palindromeService) {
System.out.println("------ test -----");
assertTrue(palindromeService.palindrome(echoService.echo("CABAC")));
assertFalse(palindromeService.palindrome(echoService.echo("TEST")));
}
}
This test is testing EchoService and PalindromeService.
As you can see, these are given to the test code as plain parameters to the test method as interfaces.
So where are the concrete objects?
in the Fixtures: here is an example of a tested system in-memory SlowServiceFixture
and FasterServiceFixture
:
public class SlowServiceFixture implements Fixture {
EchoService echoService;
PalindromeService palindromeService;
@Override
public void setUp() {
echoService = s -> new StringBuilder(s).reverse().reverse().reverse().reverse().toString();
this.palindromeService = s -> new StringBuilder(s).reverse().reverse().reverse().reverse().reverse().toString().equals(s);
System.out.println("service.init");
}
@Override
public <T> T proxyFor(Class<? extends T> aClass) {
if (aClass.isAssignableFrom(EchoService.class)) {
return aClass.cast(echoService);
}
if (aClass.isAssignableFrom(PalindromeService.class)) {
return aClass.cast(palindromeService);
} else {
return null;
}
}
@Override
public void tearDown() {
System.out.println("echo service kill");
}
}
And...
public class FasterServiceFixture implements Fixture {
EchoService echoService;
PalindromeService palindromeService;
@Override
public void setUp() {
echoService = s -> new StringBuilder(s).toString();
this.palindromeService = s -> new StringBuilder(s).reverse().toString().equals(s);
System.out.println("service.init");
}
@Override
public <T> T proxyFor(Class<? extends T> aClass) {
if (aClass.isAssignableFrom(EchoService.class)) {
return aClass.cast(echoService);
}
if (aClass.isAssignableFrom(PalindromeService.class)) {
return aClass.cast(palindromeService);
} else {
return null;
}
}
@Override
public void tearDown() {
System.out.println("echo service kill");
}
}
Obviously, there are more options, like:
public class DockerfileFixture implements Fixture {
private EchoService echoService;
private PalindromeService palindromeService;
GenericContainer genericContainer;
@Override
public void setUp() {
ImageFromDockerfile imageFromDockerfile =
new ImageFromDockerfile()
.withDockerfileFromBuilder(
builder -> builder.from("ubuntu").entryPoint("sleep infinity").build());
genericContainer = new GenericContainer<>(imageFromDockerfile);
genericContainer.start();
echoService =
s -> {
try {
return genericContainer.execInContainer("echo", s).getStdout().trim();
} catch (UnsupportedOperationException
| IOException
| InterruptedException ignoredException) {
return ignoredException.getMessage();
}
};
palindromeService =
s -> {
try {
StringBuilder cmd =
new StringBuilder("if [ \"`echo ")
.append(s)
.append(" | rev`\" = \"")
.append(s)
.append("\" ];")
.append(" then echo true;")
.append(" else echo false;")
.append("fi");
ExecResult execResult = genericContainer.execInContainer("/bin/bash","-c", cmd.toString());
return Boolean.valueOf(execResult.getStdout().trim());
} catch (UnsupportedOperationException
| IOException
| InterruptedException ignoredException) {
ignoredException.printStackTrace();
return false;
}
};
}
@Override
public <T> T proxyFor(Class<? extends T> clasz) {
if (EchoService.class.isAssignableFrom(clasz)) {
return clasz.cast(echoService);
} else if (PalindromeService.class.isAssignableFrom(clasz)) {
return clasz.cast(palindromeService);
}
return null;
}
@Override
public void tearDown() {
genericContainer.close();
}
}
which will do the same in dockers!