Хочется написать красивый тест. Пытаюсь использовать Test.createStub. Может у кого есть нормальный пример, а не тот непонятный ужас из справки.
Спасибо.
Глянул документацию, но не совсем уловил изюминку данной фичи. Можешь по простому рассказать нафига данная штука? Может на реальном примере ![]()
Красивый это тот, который понятен всем по уровню сложности и аккуратно оформлен для читабельности.
Хорошо - когда сеньор написал код, а любой джуниор его понял.
Плохо - когда джуниор написал код, а понять его смог только хороший сеньор.
Даже Salesforce предупреждает, что эта фича предполагает продвинутый уровень владения Apex и глубокое понимание юнит тестирования и mock фрэймворков.
Наверное, лучше всё таки разобрать пример из справки, т.к. он самый базовый для методов из пары строк. Специально простые примеры вряд ли кто-то делает и хранит, а показывать проектный код никто не захочет, да и сложнее он будет точно.
Ну, к примеру, нам не нужно использовать конструкции типа Test.isRunningTest() или передавать лишние параметры в методы только ради особого тестового сценария выполнения. Мы мокаем(в коде из справки) DateHelper класс и возвращаем(в MockProvider.handleMethodCall() методе) нужное нам значение даты(а не текущее, как это делает реальный метод Date.today().format()), если метод возвращает тип String.
Код в справке максимально упростили. Там вообще желательно было бы использовать не возвращаемый тип, а имя метода да и без такого харкода делать.
п.с. я ещё не пользовался этим stub фреймворком, т.к. просто не было времени раньше на проектах(приходилось фичи пилить, а не тесты оптимизировать), но сейчас с радостью обсужу со всеми, раз уже есть время.
Спасибо.
Уже года 3 как использую свой фреймворк для тестов. В последнее время добавил стабы. Все тестовые данные храняться в одном или нескольких статик ресурсах и в тестах подгружаются автоматически.
Мой случай:
Дано:
Требуется: Протестировать как нормальный логин, так и неправильный. Минимальные изменения в тестируемый код.
Решение:
Очень прошу отписаться по теме, на сколько мой код читабельный и правильно ли я понял идею этого API.
Заранее благодарен.
global without sharing class LoginForm_Ctrl { public Boolean loginFailed { get; private set; }
public String username { get; set; }
public String password { get; set; }
public LoginForm_Ctrl() {}
public PageReference login() {
PageReference page = stub.site_login(username, password, '/');
loginFailed = (page == null);
return page;
}
@TestVisible
LoginForm stub = this;
public PageReference site_login(String username, String password, String startUrl) {
return Site.login(username, password, startUrl);
}
}
@IsTest
global class LoginForm_Test implements StubProvider {@IsTest
static void login_behaviour_test() {
LoginForm ctrl;LoginForm ss_loginOk = (LoginForm) Test.createStub(LoginForm.class, new LoginForm_Test(LoginFormStates.LOGIN_OK, '/'));
LoginForm ss_loginKo = (LoginForm) Test.createStub(LoginForm.class, new LoginForm_Test(LoginFormStates.LOGIN_KO, null));Test.setCurrentPage(Page.LoginForm);
System.runAs([SELECT Id FROM User WHERE CommunityNickname = 'SiteName' LIMIT 1][0]) {
Test.startTest();
{
ctrl = new LoginForm();ctrl.username = 'aaa';
ctrl.password = 'aaa';ctrl.stub = ss_loginOk;
System.assertEquals('/', ctrl.login().getUrl());
System.assert(!ctrl.loginFailed);ctrl.stub = ss_loginKo;
System.assertEquals(null, ctrl.login());
System.assert(ctrl.loginFailed);
}
Test.stopTest();
}
}
enum LoginFormStates { LOGIN_OK, LOGIN_KO }
LoginFormStates lsf;String refUrl;
LoginForm_Test(LoginFormStates lsf, String refUrl) {
this.lsf = lsf;
this.refUrl = refUrl;
}public Object handleMethodCall(
Object stubbedObject, String stubbedMethodName, Type returnType, List<Type> listOfParamTypes,
List<String> listOfParamNames, List<Object> listOfArgs
) {
if ('site_login'.equals(stubbedMethodName)) {
return LoginFormStates.LOGIN_OK.equals(lsf) ? new PageReference(refUrl) : null;
}return null;
}
}
public without sharing class LoginFormHelper { public enum State { LOGIN_OK, LOGIN_KO }
public PageReference siteLogin(String username, String password, String startUrl) {
return Site.login(username, password, startUrl);
}
}
public without sharing class LoginFormCtrl { public Boolean loginFailed { get; private set; }
public String username { get; set; }
public String password { get; set; }
@TestVisible
private LoginFormHelper helper;
public LoginFormCtrl() {
helper = new LoginFormHelper();
}
public PageReference login() {
PageReference page = helper.siteLogin(username, password, '/');
loginFailed = page == null;
return page;
}
}
@isTest
public class LoginFormHelperMock implements StubProvider {private LoginFormHelper.State state;
private String refUrl;public LoginFormCtrlMock(LoginFormHelper.State state, String refUrl) {
this.state = state;
this.refUrl = refUrl;
}public Object handleMethodCall(Object stubbedObject, String stubbedMethodName,
Type returnType, List<Type> listOfParamTypes, List<String> listOfParamNames,
List<Object> listOfArgs) {
Object returnValue;
if ('site_login'.equals(stubbedMethodName)) {
returnValue = LoginFormHelper.State.LOGIN_OK == state
? new PageReference(refUrl)
: null;
}return returnValue;
}
}
@isTest
private class LoginFormCtrlTest {@isTest
private static void testLoginOk() {
Test.setCurrentPage(Page.LoginForm);LoginFormCtrl ctrl = new LoginFormCtrl();
ctrl.helper = getHelperMock(LoginFormHelper.State.LOGIN_OK, '/');User communityUser = getCommunityUser();
System.runAs(communityUser) {
Test.startTest();ctrl.username = 'aaa';
ctrl.password = 'aaa';System.assertEquals('/', ctrl.login().getUrl());
System.assert(!ctrl.loginFailed);Test.stopTest();
}
}@isTest
private static void testLoginKo() {
Test.setCurrentPage(Page.LoginForm);LoginFormCtrl ctrl = new LoginFormCtrl();
ctrl.helper = getHelperMock(LoginFormHelper.State.LOGIN_KO, null);User communityUser = getCommunityUser();
System.runAs(communityUser) {
Test.startTest();ctrl.username = 'aaa';
ctrl.password = 'aaa';System.assertEquals(null, ctrl.login());
System.assert(ctrl.loginFailed);Test.stopTest();
}
}private static LoginFormHelper getHelperMock(LoginFormHelper.State state, String refUrl) {
LoginFormHelper helperMock = (LoginFormHelper) Test.createStub(
LoginFormHelper.class,
new LoginFormHelperMock(state, refUrl)
);return helperMock;
}private static User getCommunityUser() {
User communityUser = [SELECT Id FROM User WHERE CommunityNickname = 'SiteName' LIMIT 1][0];return communityUser;
}}
Писал код прямо в редакторе сообщения. Если будут ошибки, то пиши ![]()
Эмммм.... Спасибо, конечно, но мне хотелось бы критику
.
1) Логику, которую мокают, лучше выносить в отдельный класс, чтобы проще было читать, обслуживать и тестировать.
2) Мок класс делать отдельно от тестов по той же причине.
3) В тестовом классе лучше делать отдельные методы под каждый тест кейс, а не один супер метод, который тестирует всё. В какой-то момент обслуживать такое становится просто невозможно.
4) LoginForm - непонятно какой это тип по коду(подозреваю, что это "LoginForm_Ctrl"). Да и имя переменной "stub" вносит только путанницу, т.к. скорее всего не относится к бизнес логике.
на текущий момент в проекте насчитывается более 550 классов. Если не так много кода - не считаю необходимым выносить.
Это - да, в моём примере упор был на понимание механизма и использование mock.
ууупссс. недоглядел.
В примере из хелпа dependency injection осуществляли посредством конструктора, и вот это меня путало больше всего.