Reputation: 130
I'm working on unit testing some code written by my boss. He's drawing a blank and I'm a newbie to TDD, so brainstorm with me please.
my file to be tested, EmailAssist is a helper class to a service not shown here. EmailAssist should reference several other services, including sectionService, as shown.
class EmailAssist {
def sectionService
//condensed to relevant items
List<CommonsMultipartFile> attachments
Map emailMap =[:]
User user
Boolean valid
public EmailAssist(){
valid = false
}
public EmailAssist(GrailsParameterMap params, User user){
//irrelevant code here involving snipped items
this.setSections(params.list('sections'))
//series of other similar calls which are also delivering an NPE
}
//third constructor using third parameter, called in example but functionally
//similar to above constructor.
//definition of errant function
void setSections(List sections) {
emailMap.sections = sectionService.getEmailsInSectionList(sections, user)
}
The section of SectionService that is being called is as follows.
Set<String> getEmailsInSectionList(List<String> sections, User user) {
if(sections && user){
//code to call DB and update list
}
else{
[]
}
My testing is not providing a section, so this should return an empty list, especially since I can't even access the DB in the unit test.
The unit test is as follows. This is using mockFor, because it didn't appear that spock's mock functionality is what I needed.
@TestMixin(GrailsUnitTestMixin)
class EmailAssistSpec extends Specification {
@Shared
GrailsParameterMap params
@Shared
GrailsMockHttpServletRequest request = new GrailsMockHttpServletRequest()
@Shared
User user
@Shared
def sectionService
def setup() {
user = new User(id: 1, firstName: "1", lastName: "1", username: "1", email: "[email protected]")
def sectionServiceMock = mockFor(SectionService)
sectionServiceMock.demand.getEmailsInSectionList() {
[]
}
sectionService = sectionServiceMock.createMock()
}
def cleanup(){
}
void testGetFiles(){
when:
//bunch of code to populate request
params = newGrailsParameterMap([:], request)
EmailAssist assist = new EmailAssist(params, request, user)
//Above constructor call generates NPE
The exact NPE is as follows: java.lang.NullPointerException: Cannot invoke method getEmailsInSectionList() on null object at
emailMap.sections = sectionService.getEmailsInSectionList(sections, user)
Which is the body of my setSections function, for those of you playing along at home. The NPE stack originates in the constructor call in my testing file. I've also tried using spock-style mocking, but the service is still being considered null. The worst part is that the constructor isn't even what this test is supposed to be testing, it's just refusing to pass this along and as a result making the tests impossible to run.
If there's any more details I can provide to clarify things, let me know, thanks!
Edit: I short circuited the setters in the constructor to complete tests, but that leaves some obvious holes in test coverage that I can't figure out how to fix. Maybe my mocking is located in the wrong place? Spock's mocking documentation isn't very handy for complex functions.
Upvotes: 3
Views: 3976
Reputation: 11921
You're getting the NPE because sectionService
within the EmailAssist
is not being created. Your constructor for EmailAssist
calls sectionService.getEmailsInSectionList(sections, user)
and because sectionService
is null, you get a NPE.
Although you create a mock called sectionService
in the test setup()
, it doesn't get auto wired / injected into the EmailAssist
class. You get very limited auto wiring in Grails unit tests - the documentation isn't very clear on what beans actually get created (and I'm relatively new to Grails/Groovy).
You need to inject the sectionService
as you create emailAssist
, otherwise it's too late to escape the NPE.
What happens if you modify your call to the constructor in your unit test to be:
@TestMixin(GrailsUnitTestMixin)
class EmailAssistSpec extends Specification {
@Shared
GrailsParameterMap params
@Shared
GrailsMockHttpServletRequest request = new GrailsMockHttpServletRequest()
@Shared
User user
@Shared
def mockSectionService = Mock(SectionService)
def setup() {
user = new User(id: 1, firstName: "1", lastName: "1", username: "1", email: "[email protected]")
}
def cleanup(){
}
void testGetFiles(){
given: "an EmailAssist class with an overridden constructor"
EmailAssist.metaClass.constructor = { ParamsType params, RequestType request, UserType user ->
def instance = new EmailAssist(sectionService: mockSectionService)
instance // this returns instance as it's the last line in the closure, but you can put "return instance" if you wish
}
// note that I've moved the population of the request to the given section
//bunch of code to populate request
params = newGrailsParameterMap([:], request)
// this is the list of parameters that you expect sectionService will be called with
def expectedSectionList = ['some', 'list']
when: "we call the constructor"
EmailAssist assist = new EmailAssist(params, request, user)
then: "sectionService is called by the constructor with the expected parameters"
1 * mockSectionService.getEmailsInSectionList(expectedSectionList, user)
// replace a parameter with _ if you don't care about testing the parameter
This answer is based on the blog post from Burt Beckwith here.
Upvotes: 1