Reputation: 3128
I have a lot of similar classes (actually it is different types of events with one parent class). It is about 30 classes already and the number will be growing. Every class has its own logic to process, but there several fields that are exists in every class. I want to be sure every event's flow is taking care of common fields. It is become more complex, because of adding new event types and adding new flows. The best approach will be to create some dynamic test that will be checking common fields are processed. Saying 'dynamically' I mean the ability of the test automatically discover new classes and put them into the test pack. We are using spock, but it is not possible to dynamically generate 'where' section of test. I come with quite a strange approach that is not working, but illustrate my idea:
def "dynamic test"() {
given:
def classes = methodToGetListOfEventClass()
when:
for(Class clazz : classes) {
ParentEvent event = clazz.getDeclaredConstructor().newInstance() as ParentEvent
service.sendEvent(event)
}
}
then:
for(Class clazz : classes) {
ParentEvent event = clazz.getDeclaredConstructor().newInstance() as ParentEvent
1 * sendExternalEvent("someId", event.getClass().getName(), Collections.emptyMap())
//check common fields exists
}
}
}
}
So I just try to create an instance of every class, pass it into the event handler and check created external event has all common fields set. It looks ugly and does not work. Is any suggestion on how to implement such a dynamic test?
Upvotes: 0
Views: 696
Reputation: 67317
You can use dynamic data pipes. Here is a simple example, based on your pseudo code and the limited information you provided. Because you did not say if you use Spock 1.3 or 2.x, I made sure that the example works on 1.3, too.
Given a situation as follows (all Groovy code, but the classes under test can be Java ones, too):
interface Event {
void init()
void sendExternalEvent(String id, String className, Map options)
}
class Service {
void sendEvent(Event event) {
event.sendExternalEvent("123", event.class.name, [:])
}
}
abstract class BaseEvent implements Event {
private static final Random random = new Random()
private static final String alphabet = (('A'..'Z') + ('0'..'9')).join()
protected int id
protected String name
@Override
void init() {
id = 1 + random.nextInt(100)
name = (1..10).collect { alphabet[random.nextInt(alphabet.length())] }.join()
}
}
class FirstEvent extends BaseEvent {
@Override
void sendExternalEvent(String id, String className, Map options) {}
String doFirst() { "first" }
}
class SecondEvent extends BaseEvent {
@Override
void sendExternalEvent(String id, String className, Map options) {}
String doSecond() { "second" }
}
class ThirdEvent extends BaseEvent {
@Override
void sendExternalEvent(String id, String className, Map options) {}
int doThird() { 3 }
}
You can implement your dynamic test for BaseEvent
subclasses like this:
import spock.lang.Specification
import spock.lang.Unroll
class DynamicBaseClassTest extends Specification {
@Unroll("verify #className")
def "basic event class functionality"() {
given:
def service = new Service()
def event = Spy(baseEventClass.getConstructor().newInstance())
when:
event.init()
then:
// '.id' and '.name' should be enough, but on Spock 2.1 there is a problem
// when not explicitly using the '@' notation for direct field access.
event.@id > 0
[email protected]() == 10
when:
service.sendEvent(event)
then:
1 * event.sendExternalEvent(_, event.class.name, [:])
where:
baseEventClass << getEventClasses()
className = baseEventClass.simpleName
}
static List<Class<? extends BaseEvent>> getEventClasses() {
[FirstEvent, SecondEvent, ThirdEvent]
}
}
Try it in the Groovy web console.
The notable things are:
where:
baseEventClass << getEventClasses()
The data pipe is declared to call a data provider method, just like in your example. What getEventClasses()
does, is totally up to you: return a fixed list, scan the classpath or whatever.
def event = Spy(baseEventClass.getConstructor().newInstance())
The spy is necessary to have both the real behaviour for the class under test - you do not want to mock it, of course - and the ability to verify interactions on it later:
then:
1 * event.sendExternalEvent(_, event.class.name, [:])
BTW, if you are unfamiliar with @Unroll
, it makes the spec look like this in an IDE or a test report:
Upvotes: 2