Pergunta

I am trying to write a unit test for one of my domain classes right now. I cheated, I know the code works by use of the code however... I want to make sure I have some automated tests in place.

I am getting the following error:

Test a sponsor(some.vendor.Vendor2Spec)
 |
java.lang.NullPointerException: Cannot invoke method getLevel() on null object
        at some.vendor.Vendor.getSponsorLevel(Vendor.groovy:111)
        at some.vendor.Vendor2Spec.Test a sponsor(Vendor2Spec.groovy:29)
|Completed 1 unit test, 1 failed in 0m 3s
.................Tests FAILED

In the following code I have indicated my line numbers where they are being called out as errors.

My Vendor looks like:

class Vendor {
  def sponsorService

  SponsorLevel getSponsorLevel(){
    return sponsorService.getLevel(this)  // Line 111
  }
}

And my test is setup as follows:

@TestFor(Vendor)
@TestMixin(GrailsUnitTestMixin)
class Vendor2Spec  extends Specification{

  @Shared
  def sponsorService = new SponsorService()

  def setup() {
  }

  def cleanup() {
  }

  void "Test a sponsor"(){
    when: 'A Sponsor donates $5'
    def vendor = new Vendor(cashDonation: 5, sponsorService: sponsorService)
    then: 'Amount Owed should be $5'
    vendor.getAmountDue().equals(new BigDecimal("5"))
    vendor.getSponsorLevel() == SponsorLevel.DIAMOND  // Line 29

    when:"A Sponsor donates an item of value"
    vendor = vendor = new Vendor(itemValue: 5)
    then: 'Amount Due is $0'
    vendor.getAmountDue().equals(new BigDecimal("0"))
    vendor.sponsorLevel == SponsorLevel.DIAMOND
  }

}

When I started out I was not newing off my sponsorService and it kept complaining about the nullness so... I tried mocking (might have been doing it wrong) but... I need to test the objects use of the service so... I don't think I need a mock.

The service looks like:

class SponsorService {
  static transactional = false

  def getLevel(Vendor vendor){
    if(!vendor){
      return null
    }
    BigDecimal sponsoringAmount = BigDecimal.ZERO

    sponsoringAmount = sponsoringAmount.add(vendor.cashDonation ?: BigDecimal.ZERO)
    sponsoringAmount = sponsoringAmount.add(vendor.itemValue ?: BigDecimal.ZERO)

    return SponsorLevel.getSponsoringLevel(sponsoringAmount)
  }
}

And the enum:

enum SponsorLevel {
    PLATINUM("Platinum"),
  GOLD("Gold"),
  SILVER("Silver"),
  BRONZE("Bronze"),
  DIAMOND("Diamond")

  final String label

  private SponsorLevel(String label){
    this.label = label
  }

  public static SponsorLevel getSponsoringLevel(BigDecimal sponsoringAmount){
    if(!sponsoringAmount){
      return null
    }    
    if(sponsoringAmount.compareTo(new BigDecimal("3000")) >= 0){
      return PLATINUM
    }
    if(sponsoringAmount.compareTo(new BigDecimal("2000")) >= 0){
      return GOLD
    }
    if(sponsoringAmount.compareTo(new BigDecimal("1000")) >= 0){
      return SILVER
    }
    if(sponsoringAmount.compareTo(new BigDecimal("500")) >= 0){
      return BRONZE
    }
    if(sponsoringAmount.compareTo(new BigDecimal("1")) >= 0){
      return DIAMOND
    }
    return null
  }
}
Foi útil?

Solução

Generally speaking, Service classes should be mocked when called from other classes for Unit Tests, otherwise, you'd want to write an Integration test.

Personally, if that is all your service is doing I'd just make that a method on the domain itself:

class Vendor {
  def sponsorService

  SponsorLevel getSponsorLevel(){
    BigDecimal sponsoringAmount = BigDecimal.ZERO

    sponsoringAmount = sponsoringAmount.add(this.cashDonation ?: BigDecimal.ZERO)
    sponsoringAmount = sponsoringAmount.add(this.itemValue ?: BigDecimal.ZERO)

    return SponsorLevel.getSponsoringLevel(sponsoringAmount)
  }
}

Your service isn't transactional, your getLevel() isn't doing anything with the database, and the method is specific to your Vendor Domain. So to me, it makes more sense to make this a Domain method on Vendor. It simplifies your code and your test.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top