2013-07-09 3 views
7

Dire che ho un controller con un'azione che riceve due parametri.Come testare un controller dell'unità in gioco framework 2 scala

invoca due servizi, uno con ogni parametro, i servizi sia stringhe di ritorno

ciascuna di queste stringhe vengono passate come argomenti a un modello

il risultato viene passato al Ok e restituito.

Voglio scrivere un semplice test di unità che assicura: 1 - I servizi corretti sono invocate con i parametri corretti 2 - I valori di ritorno dei servizi sono passati al corretto attributi del modello

Cosa è il modo migliore per farlo?

+0

Perché non utilizzare ScalaCheck/ScalaTest/Specs2 .0? – 4lex1v

+0

In particolare, come si crea un test utilizzando specs2.0 per intercettare la chiamata al servizio e la chiamata al modello? Gli esempi che ho potuto vedere testano l'applicazione nel suo complesso (integrata) –

risposta

2

Utilizzando Mockito con Specs2, eseguo servizi di simulazione per verificare le loro chiamate di metodo.

Il mio controller è istanziato da Spring. Ciò mi consente di considerare che è come class anziché object. => Questo è essenziale per rendere testabile controller. Ecco un esempio:

@Controller 
class MyController @Autowired()(val myServices: MyServices) extends Controller 

Per abilitare Primavera per i controller, è necessario definire un oggetto Global, come il gioco! documentazione spiega:

object Global extends GlobalSettings { 

    val context = new ClassPathXmlApplicationContext("application-context.xml") 

    override def getControllerInstance[A](controllerClass: Class[A]): A = { 
    context.getBean(controllerClass) 
    } 
} 

Il mio test di unità non ha bisogno di Spring; Ho appena passato i collaboratori (mock) al costruttore.

Tuttavia, per quanto riguarda il modello renderizzato, eseguo solo il test del tipo di risultato (Ok, BadRequest, Reindirizzamento ecc ...). Infatti, ho notato che non è affatto semplice fare il mio test per scansionare l'intero modello renderizzato nei dettagli (parametri inviati ad esso ecc.), Con solo test delle unità.

Quindi, per affermare che il modello giusto viene chiamato con gli argomenti giusti, mi fido dei miei test di accettazione che eseguono Selenium, o un possibile test funzionale, se preferisci, per cercare l'intero risultato previsto.

2 - I valori di ritorno dai servizi vengono passati al corretto attributi del modello

E 'abbastanza facile da controllare per vedere that..How? Affidandoti al compilatore! Preferisci passare alcuni tipi personalizzati al tuo modello invece di semplici primitivi, per esempio: phone: String diventerebbe: phone: Phone. (un oggetto di valore semplice). Pertanto, non c'è paura di passare gli attributi in un ordine non previsto al modello (nel test di unità o nel codice di produzione reale). Il compilatore in effetti avviserà.

Ecco un esempio di uno dei miei test di unità (semplificato) utilizzando le specifiche2: (Si noterà l'uso di un wrapper: WithFreshMocks). Questo case class consentirebbe di aggiornare tutte le variabili (mock in questo caso) test dopo il test. Quindi un buon modo per resettare i mock.

class MyControllerSpec extends Specification with Mockito { 

     def is = 
     "listAllCars should retrieve all cars" ! WithFreshMocks().listAllCarsShouldRetrieveAllCars 

     case class WithFreshMocks() { 

     val myServicesMock = mock[MyServices] 
     val myController = new MyController(myServicesMock) 

     def listAllCarsShouldRetrieveAllCars = { 
      val FakeGetRequest = FakeRequest() //fakeRequest needed by controller 
      mockListAllCarsAsReturningSomeCars() 
      val result = myController.listAllCars(FakeGetRequest).asInstanceOf[PlainResult] //passing fakeRequest to simulate a true request 
      assertOkResult(result). 
      and(there was one(myServicesMock).listAllCars()) //verify that there is one and only one call of listAllCars. If listAllCars would take any parameters that you expected to be called, you could have precise them. 
     } 

     private def mockListAllCarsAsReturningSomeCars() { 
      myServicesMock.listAllCars() returns List[Cars](Car("ferrari"), Car("porsche")) 
     } 

     private def assertOkResult(result: PlainResult) = result.header.status must_== 200 

     } 
+1

Mi è venuta in mente una soluzione che mi consenta di simulare i miei modelli, ma approvo di utilizzare il sistema di tipi per ottenere errori del compilatore piuttosto che errori di test quando utilizzando argomenti errati –

2

Quindi, mi si avvicinò con una soluzione modello torta e Mockito base:

dato il servizio:

trait Service { 
    def indexMessage : String 
} 

trait ServiceImpl { 
    def indexMessage = { 
    "Hello world" 
    } 
} 

Allora il controllore si presenta come:

object Application extends ApplicationController 
        with ServiceImpl { 
    def template = views.html.index.apply 
} 

trait ApplicationController extends Controller 
          with Service { 
    def template: (String) => play.api.templates.Html 

    def index = Action { 
    Ok(template("controller got:" + indexMessage)) 
    } 
} 

E il test ha il seguente aspetto:

class ApplicationControllerSpec extends Specification with Mockito { 
    "cake ApplicationController" should { 
     "return OK with the results of the service invocation" in { 
     val expectedMessage = "Test Message" 
     val m = mock[(String) => play.api.templates.Html] 

     object ApplicationControllerSpec extends ApplicationController { 
      def indexMessage = expectedMessage 
      def template = m 
     } 

     val response = ApplicationControllerSpec.index(FakeRequest()) 

     status(response) must equalTo(OK) 
     there was one(m).apply(Matchers.eq("controller got:" + expectedMessage)) 
     } 
    } 
} 

Ho avuto un sacco di problemi nel far funzionare Mockito.
Richiede una dipendenza in più e ho avuto un sacco di problemi a lavorare fuori come utilizzare le matchers a Scala (io sono abbastanza comodo utilizzarlo in java)

In definitiva credo che la risposta di cui sopra è meglio, evitare di utilizzare String e altri tipi primitivi in ​​cui è possibile racchiuderli in tipi di attività specifici, quindi vengono visualizzati avvisi del compilatore.

Inoltre, in genere eviterei di fare cose come il "controller ottenuto:" prefisso nel controller.

E 'lì, in questo caso in modo da poter verificare che ha attraversato, nel mondo reale che dovrebbe essere fatto da qualche altro componente (i controllori sono solo per l'impianto idraulico IMO)

+0

Non funziona con la riproduzione 2.3.8 – zengr