2015-10-10 3 views
1

Я пишу свой первый тест Spock и читаю документы по адресу mocking interactions, но я до сих пор не вижу «лес через деревья» на нескольких элементах.Определение Spock mock behaviors

У меня есть класс, MyRealm, который выполняет аутентификацию для моего приложения. Он имеет две зависимости: AuthService и ShiroAdapter. Первое, что я хотел бы высмеять, и последнее, что я хочу оставить как есть (если вообще возможно). Это связано с тем, что AuthService фактически делает подключение к LDAP с бэкэнд, поэтому я хочу издеваться над ним. Но ShiroAdapter просто определяет несколько методов утилиты, которые преобразуют мои объекты в объекты безопасности Apache Shiro (принципы, разрешения и т. Д.). Таким образом, его можно оставить без посмеивания (methinks).

class MyRealmSpec extends Specification { 
    MyRealm realm 

    def setup() { 
     AuthService authService = Mock(AuthService) 
     // configure 'authService' mock <-- ????? 

     ShiroAdapter shiroAdapter = new ShiroAdapter() 

     realm = new MyRealm(authService: authService, 
      shiroAdapter: shiroAdapter) 
    } 

    def "authenticate throws ShiroException whenever auth fails"() { 
     when: 
     realm.authenticate('invalid_username', 'invalid_password') 

     then: 
     Throwable throwable = thrown() 
     ShiroException.isAssignableFrom(throwable) 
    } 
} 

Я верю я очень близко, но я изо всех сил, чтобы настроить макет вести себя так, как я хочу его для испытания. Документы Spock (связанный выше) только, кажется, документируют, как проверять количество раз вызывается метод mock. Меня это не интересует.

Здесь MyRealm#authenticate(String,String) звонки AuthService#doAuth(String,String) под капотом. Поэтому мне нужен мой макет AuthService экземпляр, чтобы просто вернуть false (с указанием отказавшего auth) или выбросить ServiceFaulException, если что-то произошло неожиданно.

Любые идеи, как я могу это сделать?

ответ

1

Вы очень близки, легкий, короткий способ проверить тип исключенного исключения - поставить класс Exception в круглые скобки. Пример:

def "authenticate throws ShiroException whenever auth fails"() { 
    when: 
    realm.authenticate('invalid_username', 'invalid_password') 

    then: 
    thrown(ShiroException) 

} 

Вам также необходимо высмеять вызов службы LDAP и имитировать исключение или неудачный вход в систему. Макетные операции идут в пункте then вашего теста.

def "authenticate throws ShiroException whenever auth fails"() { 

    setup: 
    String invalidUserName = 'invalid_username' 
    String invalidPassword = 'invalid_password' 

    when: 
    realm.authenticate(invalidUserName, invalidPassword) 

    then: 
    1 * authService.doAuth(invalidUserName, invalidPassword) >> returnClosure 
    thrown(ShiroException) 

    where: 
    returnClosure << [{throw new ShiroException()}, { false }] 
} 

Обратите внимание, что вам нужно будет иметь аргументы в отношении фальшивых утверждений или использовать сопоставление с дикой картой.

Чтобы соответствовать любой строке вы можете использовать синтаксис подчеркивания:

1 * authService.doAuth(_, _) >> false 
+0

Thanks @Durandal (+1) - как я могу достичь логики «* OR *» на этикетке 'then'? Как я могу изменить вещи для достижения логики «* И *» там? Еще раз спасибо! – smeeb

+0

И неявна в предложениях 'then', каждый притворный вызов или условный список проверяется в указанном порядке; Я просто помещал туда ИЛИ, чтобы показать пример возврата исключения и возврата логического. Вы можете добавить предложение 'where', чтобы выполнить оба теста. Я обновлю, чтобы добавить этот случай. – Durandal

0

Есть несколько другого поведения объектов вы можете быть заинтересованы в

  • Заглушки - Вы только определить, что получает. (0)

    MyObject obj = Stub{method >> null} 
    
  • Mocks - Вы определяете, что возвращается и/или сколько раз вызывается метод

    MyObject obj = Mock {1..3 methodCall >> false} 
    
  • Spies - Он создает свой объект, но вы можете переопределить конкретные методы как макет (и ваши переопределяет все еще может совершать вызовы к исходному коду)

    MyObject obj = Spy {methodCall >> false} 
    obj.otherMethodCall() // Calls code like normal 
    obj.methodCall() // Returns false like we told it to 
    

Похоже, вам нужен заглушка, но вы можете использовать макет без каких-либо проблем.Я упоминаю шпиона, потому что это спасатель жизни, если ваш объект когда-либо сам зависит от себя (в будущем).


def "authenticate throws ShiroException whenever auth fails"() { 
    given: 
     AuthService authService = Stub(AuthService) 
     authService.doAuth(_,_) >> expectedError 
     MyRealm realm = new MyRealm(
      authService: authService, 
      shiroAdapter: new ShiroAdapter()) 
    when: 
     realm.authenticate("just enough to get", "to the doAuth method") 
    then: 
     thrown(ShiroException) 
    where: 
     expectedError << [ShiroException, /*other exceptions this method has to test*/] 
} 

Данные/логика разделения не требуется, но это хороший подход для создания тестов более гибкими и ремонтопригодны. Хотя в этом случае это действительно не нужно, поскольку у вас есть только одно исключение для броска.

Я бы фактически отделил неудавшиеся проверки подлинности и проверки подлинности с исключительной проверкой. Они смотрят на принципиально разные типы поведения, и логика тестирования для проверки обеих ситуаций несколько отличается. В интересах ремонтопригодности/гибкости в ваших интересах избегать слишком много испытаний (или слишком мало) при каждом тестировании.

def "authenticate throws ShiroException whenever auth fails"() { 
    given: 
     AuthService authService = Stub(AuthService) 
     authService.doAuth(_,_) >> { args -> 
      return args[0] == good && args[1] == good 
     } 
     MyRealm realm = new MyRealm(
      authService: authService, 
      shiroAdapter: new ShiroAdapter()) 
    expect: 
     realm.authenticate(username, password) == expectedAuthentication 

    where: 
     userName | password | expectedAuthentication 
     bad  | good |  false 
     bad  | bad |  false 
     good | good |  true 
} 

Обратите внимание на вышеупомянутом тест, это тесты ...

  1. вычисления Ложного для возвращаемого значения (тестирование теста)
  2. Любого кода, который происходит между вызовом аутентификации и doAuth()

Надеюсь, это то, что вы намереваетесь. Если в логике .authenticate() нет ничего, что могло бы сломаться (это имеет сложность наравне с методом getter или setter), этот тест в основном является пустой тратой времени. Единственный способ, которым логика может сломаться, - это что-то пошло не так в JVM (которое полностью выходит за рамки ответственности этого теста), или кто-то сделал какое-то изменение в будущем (хорошо, даже при огромном предположении, что .authenticate() содержит неразрывная базовая логика, тест имеет некоторое значение). Моя бессвязная точка вне темы (ужасно жаль); не забудьте сохранить то, что нужно знать о ваших тестах. Это поможет вам определить приоритетность тестовых случаев и при разработке лучших способов организации/разделения тестовой логики.

+0

Можете ли вы опубликовать ссылку? –

+0

http://spockframework.org/spock/docs/1.1-rc-3/index.html – ScientificMethod

Смежные вопросы