Hello everyone,
I'm exploring Spock's options for binding mocks with Guice given spock-guice. I've encountered a few problems and would like to get your advice on the matter.
The CUT looks like this
public class AppController {
@Inject private AppModel model;
@Inject private Github github;
@Inject private ApplicationEventBus eventBus;
public void loadRepositories() {
model.setState(RUNNING);
github.repositories(model.getOrganization())
.done(model.getRepositories()::addAll)
.fail(throwable -> eventBus.publishAsync(new ThrowableEvent(throwable)))
.always((state, resolved, rejected) -> model.setState(READY));
}
}
The `Github` type is the one I'm interested in mocking out. My first attempt to mock this type and define an interaction for it is shown next
@UseModules(TestModule)
class AppControllerSpec extends Specification {
private static final String ORGANIZATION = 'foo'
@Inject private AppController controller
@Inject private AppModel model
@Inject private ApplicationEventBus eventBus
@Inject private Github github
def happyPath() {
given:
Collection<Repository> repositories = TestHelper.createSampleRepositories()
Promise<Collection<Repository>, Throwable, Void> promise = new DeferredObject<Collection<Repository>, Throwable, Void>().resolve(repositories)
github.repositories(ORGANIZATION) >> promise
when:
model.organization = ORGANIZATION
controller.loadRepositories()
then:
model.repositories.size() == 3
model.repositories == repositories
1 * github.repositories(ORGANIZATION)
}
static class TestModule extends AppModule {
private final MockFactory mockFactory = new DetachedMockFactory()
@Override
protected void bindGithub() {
bind(Github).toInstance(mockFactory.Mock(Github))
}
}
}
Unfortunately the `AppController` class complains with an NPE as `grihub.repositories('foo')` returns a `null` as a default empty response. This means the interaction was ignored. Next I tried an explicit Mock in place but that caused the same NPE
@UseModules(AppModule)
class AppControllerSpec extends Specification {
private static final String ORGANIZATION = 'foo'
@Inject private AppController controller
@Inject private AppModel model
@Inject private ApplicationEventBus eventBus
def happyPath() {
given:
Collection<Repository> repositories = TestHelper.createSampleRepositories()
Promise<Collection<Repository>, Throwable, Void> promise = new DeferredObject<Collection<Repository>, Throwable, Void>().resolve(repositories)
controller.@github = Mock(Github) {
repositories(ORGANIZATION) >> promise
}
when:
model.organization = ORGANIZATION
controller.loadRepositories()
then:
model.repositories.size() == 3
model.repositories == repositories
1 * github.repositories(ORGANIZATION)
}
}
Next I turned to making the Mock a simple Stub and got the same problem. Just for kicks started to play around with wildcards and ended up with a working version
@UseModules(AppModule)
class AppControllerSpec extends Specification {
private static final String ORGANIZATION = 'foo'
@Inject private AppController controller
@Inject private AppModel model
@Inject private ApplicationEventBus eventBus
def happyPath() {
given:
Collection<Repository> repositories = TestHelper.createSampleRepositories()
Promise<Collection<Repository>, Throwable, Void> promise = new DeferredObject<Collection<Repository>, Throwable, Void>().resolve(repositories)
controller.@github = Stub(Github) {
_(ORGANIZATION) >> promise
}
when:
model.organization = ORGANIZATION
controller.loadRepositories()
then:
model.repositories.size() == 3
model.repositories == repositories
}
}
While it works it kind of defeats the purpose of using a DI container and mocks together, besides the use of a wildcard in this particular case is less than optimal. Any hints to make the code simpler, perhaps making it look like the first snippet, would be appreciated.
Spock 1.1-groovy-2.4
Groovy 2.4.12
Guice 4.1.0
Source code available at
https://github.com/aalmiray/javatrove/tree/master/github-api-01.
Thanks!